From 400e7f844f000d83b1fce79c168a94d6dab6ba01 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 12 Dec 2025 14:42:36 +0100 Subject: [PATCH 01/18] draft --- data/config/mosquitto/openwb_local.conf | 4 +- .../control/algorithm/additional_current.py | 7 ++- packages/control/algorithm/bidi_charging.py | 56 +++++++++---------- packages/control/algorithm/chargemodes.py | 44 ++++++--------- packages/control/algorithm/common.py | 42 +++++++------- .../control/algorithm/filter_chargepoints.py | 56 ++++++++++--------- .../algorithm/filter_chargepoints_test.py | 46 ++++++--------- .../algorithm/integration_test/conftest.py | 2 + .../integration_test/instant_charging_test.py | 49 ++++++++-------- packages/control/algorithm/min_current.py | 11 ++-- packages/control/algorithm/no_current.py | 4 +- .../control/algorithm/surplus_controlled.py | 22 ++++---- .../algorithm/surplus_controlled_test.py | 6 +- packages/control/bat_all.py | 4 +- packages/control/bat_all_test.py | 4 +- packages/control/chargepoint/chargepoint.py | 3 - .../control/chargepoint/control_parameter.py | 1 - packages/control/counter.py | 4 +- packages/control/counter_all.py | 17 ++++++ packages/control/ev/charge_template.py | 1 - packages/control/ev/ev.py | 1 + packages/helpermodules/command.py | 20 ++++--- packages/helpermodules/setdata.py | 3 +- packages/helpermodules/update_config.py | 35 +++++++++++- packages/modules/common/component_type.py | 2 + 25 files changed, 240 insertions(+), 204 deletions(-) diff --git a/data/config/mosquitto/openwb_local.conf b/data/config/mosquitto/openwb_local.conf index f540b3dcf1..8d37590972 100644 --- a/data/config/mosquitto/openwb_local.conf +++ b/data/config/mosquitto/openwb_local.conf @@ -1,4 +1,4 @@ -# openwb-version:19 +# openwb-version:20 listener 1886 localhost allow_anonymous true @@ -52,7 +52,7 @@ topic openWB/optional/# out 2 topic openWB/counter/config/# out 2 topic openWB/counter/set/# out 2 -topic openWB/counter/get/hierarchy out 2 +topic openWB/counter/get/# out 2 topic openWB/counter/+/module/# out 2 topic openWB/counter/+/config/# out 2 topic openWB/counter/+/get/# out 2 diff --git a/packages/control/algorithm/additional_current.py b/packages/control/algorithm/additional_current.py index 1cc76de58e..66484ce98b 100644 --- a/packages/control/algorithm/additional_current.py +++ b/packages/control/algorithm/additional_current.py @@ -18,12 +18,13 @@ def __init__(self) -> None: def set_additional_current(self) -> None: common.reset_current_by_chargemode(CONSIDERED_CHARGE_MODES_ADDITIONAL_CURRENT) - for mode_tuple, counter in common.mode_and_counter_generator(CONSIDERED_CHARGE_MODES_ADDITIONAL_CURRENT): + for counter in common.counter_generator(): preferenced_chargepoints, preferenced_cps_without_set_current = get_preferenced_chargepoint_charging( - get_chargepoints_by_mode_and_counter(mode_tuple, f"counter{counter.num}")) + get_chargepoints_by_mode_and_counter(CONSIDERED_CHARGE_MODES_ADDITIONAL_CURRENT, + f"counter{counter.num}")) if preferenced_chargepoints: common.update_raw_data(preferenced_chargepoints) - log.info(f"Mode-Tuple {mode_tuple[0]} - {mode_tuple[1]} - {mode_tuple[2]}, Zähler {counter.num}") + log.info(f"Zähler {counter.num}, Verbraucher {preferenced_chargepoints}") while len(preferenced_chargepoints): cp = preferenced_chargepoints[0] missing_currents, counts = common.get_missing_currents_left(preferenced_chargepoints) diff --git a/packages/control/algorithm/bidi_charging.py b/packages/control/algorithm/bidi_charging.py index 52161874c9..2e30d33724 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_loadmanagement_prios from helpermodules.phase_handling import voltages_mean log = logging.getLogger(__name__) @@ -15,32 +15,30 @@ def set_bidi(self): grid_counter = data.data.counter_all_data.get_evu_counter() 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) - if preferenced_cps: - log.info( - f"Mode-Tuple {mode_tuple[0]} - {mode_tuple[1]} - {mode_tuple[2]}, Zähler {grid_counter.num}") - while len(preferenced_cps): - cp = preferenced_cps[0] - zero_point_adjustment = grid_counter.data.set.surplus_power_left / len(preferenced_cps) - log.debug(f"Nullpunktanpassung für LP{cp.num}: verbleibende Leistung {zero_point_adjustment}W") - missing_currents = [zero_point_adjustment / cp.data.get.phases_in_use / - 230 for i in range(0, cp.data.get.phases_in_use)] - missing_currents += [0] * (3 - len(missing_currents)) - if zero_point_adjustment > 0: - if cp.data.set.charging_ev_data.charge_template.bidi_charging_allowed( - cp.data.control_parameter.current_plan, cp.data.set.charging_ev_data.data.get.soc): - for index in range(0, 3): - missing_currents[index] = min(cp.data.control_parameter.required_current, - missing_currents[index]) - else: - log.info(f"LP{cp.num}: Nur bidirektional entladen erlaubt, da SoC-Limit erreicht.") - missing_currents = [0, 0, 0] - else: + preferenced_cps = get_loadmanagement_prios(CONSIDERED_CHARGE_MODES_BIDI_DISCHARGE) + if preferenced_cps: + log.info(f"Verbraucher {preferenced_cps}") + while len(preferenced_cps): + cp = preferenced_cps[0] + zero_point_adjustment = grid_counter.data.set.surplus_power_left / len(preferenced_cps) + log.debug(f"Nullpunktanpassung für LP{cp.num}: verbleibende Leistung {zero_point_adjustment}W") + missing_currents = [zero_point_adjustment / cp.data.get.phases_in_use / + 230 for i in range(0, cp.data.get.phases_in_use)] + missing_currents += [0] * (3 - len(missing_currents)) + if zero_point_adjustment > 0: + if cp.data.set.charging_ev_data.charge_template.bidi_charging_allowed( + cp.data.control_parameter.current_plan, cp.data.set.charging_ev_data.data.get.soc): for index in range(0, 3): - missing_currents[index] = cp.check_min_max_current(missing_currents[index], - cp.data.get.phases_in_use) - grid_counter.update_surplus_values_left(missing_currents, voltages_mean(cp.data.get.voltages)) - cp.data.set.current = missing_currents[0] - log.info(f"LP{cp.num}: Stromstärke {missing_currents}A") - preferenced_cps.pop(0) + missing_currents[index] = min(cp.data.control_parameter.required_current, + missing_currents[index]) + else: + log.info(f"LP{cp.num}: Nur bidirektional entladen erlaubt, da SoC-Limit erreicht.") + missing_currents = [0, 0, 0] + else: + for index in range(0, 3): + missing_currents[index] = cp.check_min_max_current(missing_currents[index], + cp.data.get.phases_in_use) + grid_counter.update_surplus_values_left(missing_currents, voltages_mean(cp.data.get.voltages)) + cp.data.set.current = missing_currents[0] + log.info(f"LP{cp.num}: Stromstärke {missing_currents}A") + preferenced_cps.pop(0) diff --git a/packages/control/algorithm/chargemodes.py b/packages/control/algorithm/chargemodes.py index 0ebfa07cf3..5dbbd23673 100644 --- a/packages/control/algorithm/chargemodes.py +++ b/packages/control/algorithm/chargemodes.py @@ -2,32 +2,22 @@ # Lademodi in absteigender Priorität # Tupel-Inhalt:(eingestellter Modus, tatsächlich genutzter Modus, Priorität) -CHARGEMODES = ((Chargemode.SCHEDULED_CHARGING, Chargemode.INSTANT_CHARGING, True), - (Chargemode.SCHEDULED_CHARGING, Chargemode.INSTANT_CHARGING, False), - (None, Chargemode.TIME_CHARGING, True), - (None, Chargemode.TIME_CHARGING, False), - (Chargemode.INSTANT_CHARGING, Chargemode.INSTANT_CHARGING, True), - (Chargemode.INSTANT_CHARGING, Chargemode.INSTANT_CHARGING, False), - (Chargemode.ECO_CHARGING, Chargemode.INSTANT_CHARGING, True), - (Chargemode.ECO_CHARGING, Chargemode.INSTANT_CHARGING, False), - (Chargemode.PV_CHARGING, Chargemode.INSTANT_CHARGING, True), - (Chargemode.PV_CHARGING, Chargemode.INSTANT_CHARGING, False), - (Chargemode.SCHEDULED_CHARGING, Chargemode.PV_CHARGING, True), - (Chargemode.SCHEDULED_CHARGING, Chargemode.PV_CHARGING, False), - (Chargemode.ECO_CHARGING, Chargemode.PV_CHARGING, True), - (Chargemode.ECO_CHARGING, Chargemode.PV_CHARGING, False), - (Chargemode.PV_CHARGING, Chargemode.PV_CHARGING, True), - (Chargemode.PV_CHARGING, Chargemode.PV_CHARGING, False), +CHARGEMODES = ((Chargemode.SCHEDULED_CHARGING, Chargemode.INSTANT_CHARGING), + (None, Chargemode.TIME_CHARGING), + (Chargemode.INSTANT_CHARGING, Chargemode.INSTANT_CHARGING), + (Chargemode.ECO_CHARGING, Chargemode.INSTANT_CHARGING), + (Chargemode.PV_CHARGING, Chargemode.INSTANT_CHARGING), + (Chargemode.SCHEDULED_CHARGING, Chargemode.PV_CHARGING), + (Chargemode.ECO_CHARGING, Chargemode.PV_CHARGING), + (Chargemode.PV_CHARGING, Chargemode.PV_CHARGING), # niedrigere Priorität soll nachrangig geladen, aber zuerst entladen werden - (Chargemode.SCHEDULED_CHARGING, Chargemode.BIDI_CHARGING, False), - (Chargemode.SCHEDULED_CHARGING, Chargemode.BIDI_CHARGING, True), - (None, Chargemode.STOP, True), - (None, Chargemode.STOP, False)) + (Chargemode.SCHEDULED_CHARGING, Chargemode.BIDI_CHARGING), + (None, Chargemode.STOP),) -CONSIDERED_CHARGE_MODES_SURPLUS = CHARGEMODES[0:2] + CHARGEMODES[6:16] -CONSIDERED_CHARGE_MODES_PV_ONLY = CHARGEMODES[10:16] -CONSIDERED_CHARGE_MODES_ADDITIONAL_CURRENT = CHARGEMODES[0:10] -CONSIDERED_CHARGE_MODES_MIN_CURRENT = CHARGEMODES[0:-4] -CONSIDERED_CHARGE_MODES_NO_CURRENT = CHARGEMODES[18:20] -CONSIDERED_CHARGE_MODES_BIDI_DISCHARGE = CHARGEMODES[16:18] -CONSIDERED_CHARGE_MODES_CHARGING = CHARGEMODES[0:16] +CONSIDERED_CHARGE_MODES_SURPLUS = (CHARGEMODES[0], *CHARGEMODES[3:8]) +CONSIDERED_CHARGE_MODES_PV_ONLY = CHARGEMODES[5:8] +CONSIDERED_CHARGE_MODES_ADDITIONAL_CURRENT = CHARGEMODES[0:5] +CONSIDERED_CHARGE_MODES_MIN_CURRENT = CHARGEMODES[0:-2] +CONSIDERED_CHARGE_MODES_NO_CURRENT = (CHARGEMODES[9],) +CONSIDERED_CHARGE_MODES_BIDI_DISCHARGE = (CHARGEMODES[8],) +CONSIDERED_CHARGE_MODES_CHARGING = CHARGEMODES[0:8] diff --git a/packages/control/algorithm/common.py b/packages/control/algorithm/common.py index 11a345c128..f5e68d751f 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_loadmanagement_prios from control.algorithm.utils import get_medium_charging_current from control.chargepoint.chargepoint import Chargepoint from control.counter import Counter @@ -27,20 +27,18 @@ 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(chargemodes: Tuple[Tuple[Optional[str], str]]) -> None: + for cp in get_loadmanagement_prios(chargemodes): + cp.data.set.current = None -def mode_and_counter_generator(chargemodes: List) -> Iterable[Tuple[Tuple[Optional[str], str, bool], Counter]]: - for mode_tuple in chargemodes: - levels = data.data.counter_all_data.get_list_of_elements_per_level() - for level in reversed(levels): - for element in level: - if element["type"] == ComponentType.COUNTER.value: - counter = data.data.counter_data[f"counter{element['id']}"] - yield mode_tuple, counter +def counter_generator() -> Iterable[Counter]: + levels = data.data.counter_all_data.get_list_of_elements_per_level() + for level in reversed(levels): + for element in level: + if element["type"] == ComponentType.COUNTER.value: + counter = data.data.counter_data[f"counter{element['id']}"] + yield counter # tested @@ -112,13 +110,17 @@ def available_current_for_cp(chargepoint: Chargepoint, control_parameter = chargepoint.data.control_parameter available_current = float("inf") missing_current_cp = control_parameter.required_current - chargepoint.data.set.target_current - for i in range(0, 3): - if (control_parameter.required_currents[i] != 0 and - missing_currents[i] != available_currents[i]): - available_current = min(min(missing_current_cp, available_currents[i]/counts[i]), available_current) - if available_current == float("inf"): - available_current = missing_current_cp - return available_current + + if chargepoint.data.set.charging_ev_data.data.full_power: + return missing_current_cp + else: + for i in range(0, 3): + if (control_parameter.required_currents[i] != 0 and + missing_currents[i] != available_currents[i]): + available_current = min(min(missing_current_cp, available_currents[i]/counts[i]), available_current) + if available_current == float("inf"): + available_current = missing_current_cp + return available_current def update_raw_data(preferenced_chargepoints: List[Chargepoint], diff --git a/packages/control/algorithm/filter_chargepoints.py b/packages/control/algorithm/filter_chargepoints.py index 7af2f319f3..ea53e8e50e 100644 --- a/packages/control/algorithm/filter_chargepoints.py +++ b/packages/control/algorithm/filter_chargepoints.py @@ -8,38 +8,48 @@ log = logging.getLogger(__name__) -def get_chargepoints_by_mode_and_counter(mode_tuple: Tuple[Optional[str], str, bool], - counter: str) -> List[Chargepoint]: +def get_chargepoints_by_mode_and_counter(chargemodes: Tuple[Tuple[Optional[str], str]], + counter: str, + full_power_considered: bool = True) -> 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_loadmanagement_prios(chargemodes, full_power_considered) 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: - 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)): - valid_chargepoints.append(cp) - return valid_chargepoints +def get_loadmanagement_prios(chargemodes: Tuple[Tuple[Optional[str], str]], + full_power_considered: bool = True) -> List[Chargepoint]: + def _process_chargemodes(power_filter_func): + for chargemode in chargemodes: + valid_chargemode = [] + for item in data.data.counter_all_data.data.get.loadmanagement_prios: + if item["type"] == "ev": + for cp in data.data.cp_data.values(): + if item["id"] == cp.data.config.ev and power_filter_func(cp): + if cp.data.control_parameter.required_current != 0: + if ((cp.data.control_parameter.chargemode == chargemode[0] or + chargemode[0] is None) and + (cp.data.control_parameter.submode == chargemode[1]) and + cp not in valid and cp not in valid_chargemode): + valid_chargemode.append(cp) + valid.extend(_get_preferenced_chargepoint(valid_chargemode)) + + valid = [] + if full_power_considered: + _process_chargemodes(lambda cp: cp.data.set.charging_ev_data.data.full_power is True) + _process_chargemodes(lambda cp: cp.data.set.charging_ev_data.data.full_power is False) + else: + _process_chargemodes(lambda cp: True) + return valid def get_preferenced_chargepoint_charging( chargepoints: List[Chargepoint]) -> Tuple[List[Chargepoint], List[Chargepoint]]: - preferenced_chargepoints = _get_preferenced_chargepoint(chargepoints) preferenced_chargepoints_with_set_current = [] preferenced_chargepoints_without_set_current = [] - for cp in preferenced_chargepoints: + for cp in chargepoints: if cp.data.set.target_current == 0: log.info( f"LP {cp.num}: Keine Zuteilung des Mindeststroms, daher keine weitere Berücksichtigung") @@ -52,7 +62,6 @@ def get_preferenced_chargepoint_charging( preferenced_chargepoints_with_set_current.append(cp) return preferenced_chargepoints_with_set_current, preferenced_chargepoints_without_set_current - # tested @@ -104,10 +113,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..fe52d9047a 100644 --- a/packages/control/algorithm/filter_chargepoints_test.py +++ b/packages/control/algorithm/filter_chargepoints_test.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import List, Optional, Tuple +from typing import Dict, List from unittest.mock import Mock import pytest @@ -96,44 +96,32 @@ def mock_cp(cp: Chargepoint, num: int): @pytest.mark.parametrize( - "set_mode_tuple, required_current_1, mode_tuple_1, mode_tuple_2, expected_valid_chargepoints", + "required_current_1, loadmanagement_prios, expected_valid_chargepoints", [ - pytest.param((Chargemode.SCHEDULED_CHARGING, Chargemode.INSTANT_CHARGING, False), - 6, (Chargemode.SCHEDULED_CHARGING, - Chargemode.INSTANT_CHARGING, False), - (Chargemode.SCHEDULED_CHARGING, - Chargemode.INSTANT_CHARGING, False), + pytest.param(6, [{"type": "ev", "id": "1"}, {"type": "ev", "id": "2"}], [mock_cp1, mock_cp2], id="fits mode"), - pytest.param((Chargemode.SCHEDULED_CHARGING, Chargemode.INSTANT_CHARGING, False), - 0, (Chargemode.SCHEDULED_CHARGING, - Chargemode.INSTANT_CHARGING, False), - (Chargemode.SCHEDULED_CHARGING, - Chargemode.INSTANT_CHARGING, False), + pytest.param(0, [{"type": "ev", "id": "1"}, {"type": "ev", "id": "2"}], [mock_cp2], id="cp1 should not charge"), - pytest.param((Chargemode.SCHEDULED_CHARGING, Chargemode.INSTANT_CHARGING, False), - 6, (Chargemode.SCHEDULED_CHARGING, - Chargemode.INSTANT_CHARGING, False), - (Chargemode.SCHEDULED_CHARGING, - Chargemode.INSTANT_CHARGING, True), - [mock_cp1], id="cp2 is prioritized") + pytest.param(6, [{"type": "ev", "id": "2"}, {"type": "ev", "id": "1"}], + [mock_cp2, mock_cp1], id="cp2 is prioritized") ]) -def test_get_chargepoints_by_mode(set_mode_tuple: Tuple[Optional[str], str, bool], - required_current_1: int, - mode_tuple_1: Tuple[str, str, bool], - mode_tuple_2: Tuple[str, str, bool], +def test_get_chargepoints_by_mode(required_current_1: int, + loadmanagement_prios: List[Dict], expected_valid_chargepoints): # setup - def setup_cp(cp: Chargepoint, required_current: float, mode_tuple: Tuple[str, str, bool]) -> Chargepoint: + def setup_cp(cp: Chargepoint, required_current: float) -> Chargepoint: cp.data.control_parameter.required_current = required_current - cp.data.control_parameter.prio = mode_tuple[2] - cp.data.control_parameter.chargemode = mode_tuple[0] - cp.data.control_parameter.submode = mode_tuple[1] + cp.data.control_parameter.chargemode = Chargemode.SCHEDULED_CHARGING + cp.data.control_parameter.submode = Chargemode.INSTANT_CHARGING return cp - data.data.cp_data = {"cp1": setup_cp(mock_cp1, required_current_1, mode_tuple_1), - "cp2": setup_cp(mock_cp2, 6, mode_tuple_2)} + data.data.cp_data = {"cp1": setup_cp(mock_cp1, required_current_1), + "cp2": setup_cp(mock_cp2, 6)} + data.data.counter_all_data = CounterAll() + data.data.counter_all_data.data.get.loadmanagement_prios = loadmanagement_prios # evaluation - valid_chargepoints = filter_chargepoints.get_chargepoints_by_mode(set_mode_tuple) + valid_chargepoints = filter_chargepoints.get_loadmanagement_prios( + (Chargemode.SCHEDULED_CHARGING, Chargemode.INSTANT_CHARGING)) # assertion assert valid_chargepoints == expected_valid_chargepoints diff --git a/packages/control/algorithm/integration_test/conftest.py b/packages/control/algorithm/integration_test/conftest.py index dd91361d37..846a9da9e5 100644 --- a/packages/control/algorithm/integration_test/conftest.py +++ b/packages/control/algorithm/integration_test/conftest.py @@ -27,6 +27,7 @@ def data_() -> None: for i in range(3, 6): data.data.cp_data[f"cp{i}"].template = CpTemplate() data.data.cp_data[f"cp{i}"].data.config.phase_1 = i-2 + data.data.cp_data[f"cp{i}"].data.config.ev = 0 data.data.cp_data[f"cp{i}"].data.set.charging_ev_data = Ev(i) data.data.cp_data[f"cp{i}"].data.set.charging_ev_data.ev_template.data.max_current_single_phase = 32 data.data.cp_data[f"cp{i}"].data.get.plug_state = True @@ -49,6 +50,7 @@ def data_() -> None: data.data.counter_data["counter6"].data.config.max_total_power = 11000 data.data.counter_all_data = CounterAll() data.data.counter_all_data.data.get.hierarchy = NESTED_HIERARCHY + data.data.counter_all_data.data.get.loadmanagement_prios = [{"type": "ev", "id": 0}] data.data.counter_all_data.data.config.consider_less_charging = True data.data.io_actions = IoActions() diff --git a/packages/control/algorithm/integration_test/instant_charging_test.py b/packages/control/algorithm/integration_test/instant_charging_test.py index afc9b23551..3bda57f2c6 100644 --- a/packages/control/algorithm/integration_test/instant_charging_test.py +++ b/packages/control/algorithm/integration_test/instant_charging_test.py @@ -7,6 +7,7 @@ from control.chargemode import Chargemode from control import data, loadmanagement from control.algorithm.algorithm import Algorithm +from control.ev.ev import Ev from control.limiting_value import LimitingValue from dataclass_utils.factories import currents_list_factory @@ -43,6 +44,10 @@ def all_cp_instant_charging_3p(): control_parameter.chargemode = Chargemode.INSTANT_CHARGING data.data.cp_data[f"cp{i}"].data.get.charge_state = True data.data.cp_data[f"cp{i}"].data.get.currents = [16]*3 + data.data.cp_data[f"cp{i}"].data.set.charging_ev_data = Ev(i) + data.data.cp_data[f"cp{i}"].data.config.ev = i + data.data.counter_all_data.data.get.loadmanagement_prios = [ + {"type": "ev", "id": 3}, {"type": "ev", "id": 4}, {"type": "ev", "id": 5}] @dataclass @@ -111,6 +116,7 @@ class ParamsLimit(ParamsExpectedSetCurrent, ParamsExpectedCounterSet): expected_raw_power_left=0, expected_raw_currents_left_counter0=[21.53846153846154, 25.23076923076923, 25.23076923076923], expected_raw_currents_left_counter6=[16, 9.23076923076923, 9.23076923076923]), + # limit by unbalanced load ] @@ -137,19 +143,19 @@ def test_instant_charging_limit(params: ParamsLimit, all_cp_instant_charging_1p, @dataclass class ParamsControlParameter(ParamsExpectedSetCurrent, ParamsExpectedCounterSet): name: str = "" - prio_cp3: bool = False + full_power_cp3: bool = False submode_cp3: Chargemode = Chargemode.INSTANT_CHARGING - prio_cp4: bool = False + full_power_cp4: bool = False submode_cp4: Chargemode = Chargemode.INSTANT_CHARGING - prio_cp5: bool = False + full_power_cp5: bool = False submode_cp5: Chargemode = Chargemode.INSTANT_CHARGING cases_control_parameter = [ ParamsControlParameter(name="lift prio cp3", - prio_cp3=True, - prio_cp4=False, - prio_cp5=False, + full_power_cp3=True, + full_power_cp4=False, + full_power_cp5=False, expected_current_cp3=16, expected_current_cp4=8, expected_current_cp5=8, @@ -157,44 +163,35 @@ class ParamsControlParameter(ParamsExpectedSetCurrent, ParamsExpectedCounterSet) expected_raw_currents_left_counter0=[0]*3, expected_raw_currents_left_counter6=[0]*3), ParamsControlParameter(name="drop prio cp4", - prio_cp3=True, - prio_cp4=False, - prio_cp5=True, - expected_current_cp3=15, + full_power_cp3=True, + full_power_cp4=False, + full_power_cp5=True, + expected_current_cp3=16, expected_current_cp4=6, expected_current_cp5=10, expected_raw_power_left=690, expected_raw_currents_left_counter0=[1]*3, expected_raw_currents_left_counter6=[0]*3), - ParamsControlParameter(name="lift submode cp4", + ParamsControlParameter(name="change submode cp4", submode_cp4=Chargemode.TIME_CHARGING, - expected_current_cp3=13, - expected_current_cp4=10, - expected_current_cp5=6, + expected_current_cp3=16, + expected_current_cp4=8, + expected_current_cp5=8, expected_raw_power_left=2070, expected_raw_currents_left_counter0=[3]*3, expected_raw_currents_left_counter6=[0]*3), - # ParamsControlParameter(name="drop submode cp4", - # niedrigster instant modus erreicht - # submode_cp4=Chargemode.PV_CHARGING, - # expected_current_cp3=16, - # expected_current_cp4=6, - # expected_current_cp5=10, - # expected_raw_power_left=0, - # expected_raw_currents_left_counter0=[0]*3, - # expected_raw_currents_left_counter6=[0]*3) ] @pytest.mark.parametrize("params", cases_control_parameter, ids=[c.name for c in cases_control_parameter]) def test_control_parameter_instant_charging(params: ParamsControlParameter, all_cp_instant_charging_3p, monkeypatch): # setup - data.data.cp_data["cp3"].data.control_parameter.prio = params.prio_cp3 data.data.cp_data["cp3"].data.control_parameter.submode = params.submode_cp3 - data.data.cp_data["cp4"].data.control_parameter.prio = params.prio_cp4 + data.data.cp_data["cp3"].data.set.charging_ev_data.data.full_power = params.full_power_cp3 data.data.cp_data["cp4"].data.control_parameter.submode = params.submode_cp4 - data.data.cp_data["cp5"].data.control_parameter.prio = params.prio_cp5 + data.data.cp_data["cp4"].data.set.charging_ev_data.data.full_power = params.full_power_cp4 data.data.cp_data["cp5"].data.control_parameter.submode = params.submode_cp5 + data.data.cp_data["cp5"].data.set.charging_ev_data.data.full_power = params.full_power_cp5 data.data.counter_data["counter0"].data.set.raw_power_left = 22080 data.data.counter_data["counter0"].data.set.raw_currents_left = [32]*3 data.data.counter_data["counter6"].data.set.raw_currents_left = [16]*3 diff --git a/packages/control/algorithm/min_current.py b/packages/control/algorithm/min_current.py index 8dada55ce0..248b695f72 100644 --- a/packages/control/algorithm/min_current.py +++ b/packages/control/algorithm/min_current.py @@ -16,10 +16,12 @@ def __init__(self) -> None: pass def set_min_current(self) -> None: - for mode_tuple, counter in common.mode_and_counter_generator(CONSIDERED_CHARGE_MODES_MIN_CURRENT): - preferenced_chargepoints = get_chargepoints_by_mode_and_counter(mode_tuple, f"counter{counter.num}") + for counter in common.counter_generator(): + preferenced_chargepoints = get_chargepoints_by_mode_and_counter(CONSIDERED_CHARGE_MODES_MIN_CURRENT, + f"counter{counter.num}", + full_power_considered=False) if preferenced_chargepoints: - log.info(f"Mode-Tuple {mode_tuple[0]} - {mode_tuple[1]} - {mode_tuple[2]}, Zähler {counter.num}") + log.info(f"Zähler {counter.num}, Verbraucher {preferenced_chargepoints}") common.update_raw_data(preferenced_chargepoints, diff_to_zero=True) while len(preferenced_chargepoints): cp = preferenced_chargepoints[0] @@ -43,7 +45,8 @@ def set_min_current(self) -> None: cp.data.control_parameter.min_current, cp) else: - if mode_tuple in CONSIDERED_CHARGE_MODES_PV_ONLY: + if (cp.data.control_parameter.chargemode, + cp.data.control_parameter.submode) in CONSIDERED_CHARGE_MODES_PV_ONLY: try: if (cp.data.control_parameter.state == ChargepointState.NO_CHARGING_ALLOWED or cp.data.control_parameter.state == ChargepointState.SWITCH_ON_DELAY): diff --git a/packages/control/algorithm/no_current.py b/packages/control/algorithm/no_current.py index 0b55fed852..ef1c217216 100644 --- a/packages/control/algorithm/no_current.py +++ b/packages/control/algorithm/no_current.py @@ -2,7 +2,7 @@ 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_loadmanagement_prios log = logging.getLogger(__name__) @@ -12,7 +12,7 @@ def __init__(self) -> None: pass def set_no_current(self) -> None: - chargepoints = get_chargepoints_by_chargemodes(CONSIDERED_CHARGE_MODES_NO_CURRENT) + chargepoints = get_loadmanagement_prios(CONSIDERED_CHARGE_MODES_NO_CURRENT) for cp in chargepoints: cp.data.set.current = 0 diff --git a/packages/control/algorithm/surplus_controlled.py b/packages/control/algorithm/surplus_controlled.py index e2e894df51..db5827f4aa 100644 --- a/packages/control/algorithm/surplus_controlled.py +++ b/packages/control/algorithm/surplus_controlled.py @@ -5,8 +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, - get_chargepoints_by_mode_and_counter, +from control.algorithm.filter_chargepoints import (get_chargepoints_by_mode_and_counter, get_loadmanagement_prios, get_preferenced_chargepoint_charging) from control.algorithm.utils import get_medium_charging_current from control.chargepoint.charging_type import ChargingType @@ -28,28 +27,27 @@ def __init__(self) -> None: def set_surplus_current(self) -> None: common.reset_current_by_chargemode(CONSIDERED_CHARGE_MODES_SURPLUS) - for mode_tuple, counter in common.mode_and_counter_generator(CONSIDERED_CHARGE_MODES_SURPLUS): + for counter in common.counter_generator(): preferenced_chargepoints, preferenced_cps_without_set_current = get_preferenced_chargepoint_charging( - get_chargepoints_by_mode_and_counter(mode_tuple, f"counter{counter.num}")) + get_chargepoints_by_mode_and_counter(CONSIDERED_CHARGE_MODES_SURPLUS, f"counter{counter.num}")) cp_with_feed_in, cp_without_feed_in = self.filter_by_feed_in_limit(preferenced_chargepoints) if cp_without_feed_in: - self._set(cp_without_feed_in, 0, mode_tuple, counter) + self._set(cp_without_feed_in, 0, counter) feed_in_yield = data.data.general_data.data.chargemode_config.pv_charging.feed_in_yield if cp_with_feed_in: - self._set(cp_with_feed_in, feed_in_yield, mode_tuple, counter) + self._set(cp_with_feed_in, feed_in_yield, counter) 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_loadmanagement_prios(CONSIDERED_CHARGE_MODES_SURPLUS): if cp.data.control_parameter.state in CHARGING_STATES: self._fix_deviating_evse_current(cp) def _set(self, chargepoints: List[Chargepoint], feed_in_yield: Optional[int], - mode_tuple: Tuple[Optional[str], str, bool], counter: Counter) -> None: - log.info(f"Mode-Tuple {mode_tuple[0]} - {mode_tuple[1]} - {mode_tuple[2]}, Zähler {counter.num}") + log.info(f"Zähler {counter.num}, Verbraucher {chargepoints}") common.update_raw_data(chargepoints, surplus=True) while len(chargepoints): cp = chargepoints[0] @@ -127,7 +125,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_loadmanagement_prios(CONSIDERED_CHARGE_MODES_PV_ONLY): try: def phase_switch_necessary() -> bool: return (cp.cp_state_hw_support_phase_switch() and @@ -159,8 +157,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_loadmanagement_prios(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..46de9af168 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_loadmanagement_prios 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_loadmanagement_prios", 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_loadmanagement_prios(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 676ca7fd03..fbaadb4520 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_loadmanagement_prios from control.pv import Pv from helpermodules.constants import NO_ERROR from modules.common.abstract_device import AbstractDevice @@ -316,7 +316,7 @@ def get_power_limit(self): if self.data.config.bat_control_permitted is False: self.data.set.power_limit = None else: - chargepoint_by_chargemodes = get_chargepoints_by_chargemodes(CONSIDERED_CHARGE_MODES_CHARGING) + chargepoint_by_chargemodes = get_loadmanagement_prios(CONSIDERED_CHARGE_MODES_CHARGING) # Falls aktive Steuerung an und Fahrzeuge laden und kein Überschuss im System ist, # dann Speicherleistung begrenzen. if (self.data.config.power_limit_mode != BatPowerLimitMode.NO_LIMIT.value and diff --git a/packages/control/bat_all_test.py b/packages/control/bat_all_test.py index 1429cdd1ad..39f83a5ccb 100644 --- a/packages/control/bat_all_test.py +++ b/packages/control/bat_all_test.py @@ -209,8 +209,8 @@ def test_get_power_limit(params: PowerLimitParams, 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_loadmanagement_prios_mock = Mock(return_value=params.cps) + monkeypatch.setattr(bat_all, "get_loadmanagement_prios", get_loadmanagement_prios_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 328cfe899b..027788430b 100644 --- a/packages/control/chargepoint/chargepoint.py +++ b/packages/control/chargepoint/chargepoint.py @@ -245,7 +245,6 @@ def set_control_parameter(self, submode: str): else: self.data.control_parameter.chargemode = Chargemode( self.data.set.charge_template.data.chargemode.selected) - self.data.control_parameter.prio = self.data.set.charge_template.data.prio if self.template.data.charging_type == ChargingType.AC.value: self.data.control_parameter.min_current = self.data.set.charging_ev_data.ev_template.data.min_current else: @@ -710,7 +709,6 @@ def update(self, ev_list: Dict[str, Ev]) -> None: f"{self.data.set.charge_template.data.chargemode.selected}, Submodus: " f"{self.data.control_parameter.submode}, Phasen: " f"{self.data.control_parameter.phases}" - f", Priorität: {self.data.control_parameter.prio}" 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)) @@ -828,7 +826,6 @@ def _pub_connected_vehicle(self, vehicle: Ev): charge_template=self.data.set.charge_template.data.id, ev_template=vehicle.ev_template.data.id, chargemode=self.data.set.charge_template.data.chargemode.selected, - priority=self.data.set.charge_template.data.prio, current_plan=current_plan, average_consumption=vehicle.ev_template.data.average_consump, time_charging_in_use=True if (self.data.control_parameter.submode == diff --git a/packages/control/chargepoint/control_parameter.py b/packages/control/chargepoint/control_parameter.py index ee671f705b..112ba4d043 100644 --- a/packages/control/chargepoint/control_parameter.py +++ b/packages/control/chargepoint/control_parameter.py @@ -17,7 +17,6 @@ class ControlParameter: "topic": "control_parameter/limit"}) min_current: int = field(default=6, metadata={"topic": "control_parameter/min_current"}) phases: int = field(default=0, metadata={"topic": "control_parameter/phases"}) - prio: bool = field(default=False, metadata={"topic": "control_parameter/prio"}) required_current: float = field(default=0, metadata={"topic": "control_parameter/required_current"}) required_currents: List[float] = field(default_factory=currents_list_factory) state: ChargepointState = field(default=ChargepointState.NO_CHARGING_ALLOWED, diff --git a/packages/control/counter.py b/packages/control/counter.py index b3e6fcddf0..f3789f067b 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_loadmanagement_prios from control.algorithm.utils import get_medium_charging_current from control.chargemode import Chargemode from control.chargepoint.chargepoint import Chargepoint @@ -519,7 +519,7 @@ 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_loadmanagement_prios(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/counter_all.py b/packages/control/counter_all.py index 2eeb156d18..62583db0e0 100644 --- a/packages/control/counter_all.py +++ b/packages/control/counter_all.py @@ -52,6 +52,8 @@ class Set: class Get: hierarchy: List = field(default_factory=empty_list_factory, metadata={ "topic": "get/hierarchy"}) + loadmanagement_prios: List[Dict] = field( + default_factory=empty_list_factory, metadata={"topic": "get/loadmanagement_prios"}) def get_factory() -> Get: @@ -481,6 +483,21 @@ def check_and_add(type_name: ComponentType, data_structure): "Lastmanagements gesetzt werden kann. Bitte zuerst einen EVU-Zähler hinzufügen."), MessageType.ERROR) + # Lastmanagement-Prioritäten + def loadmanagement_prios_add_item(self, new_id: int, new_type: ComponentType) -> None: + if new_type == ComponentType.VEHICLE: + self.data.get.loadmanagement_prios.append({"id": new_id, "type": new_type.value}) + else: + raise ValueError("Derzeit können nur Fahrzeuge zu den Lastmanagement-Prioritäten hinzugefügt werden.") + + def loadmanagement_prios_remove_item(self, id: int) -> None: + for item in self.data.get.loadmanagement_prios: + if item["id"] == id: + self.data.get.loadmanagement_prios.remove(item) + return + else: + raise IndexError(f"Element {id} konnte nicht in den Lastmanagement-Prioritäten gefunden werden.") + def get_max_id_in_hierarchy(current_entry: List, max_id: int) -> int: for item in current_entry: diff --git a/packages/control/ev/charge_template.py b/packages/control/ev/charge_template.py index 11c66be9d5..916deac981 100644 --- a/packages/control/ev/charge_template.py +++ b/packages/control/ev/charge_template.py @@ -110,7 +110,6 @@ def chargemode_factory() -> Chargemode: class ChargeTemplateData: id: int = 0 name: str = "Lade-Profil" - prio: bool = False load_default: bool = False time_charging: TimeCharging = field(default_factory=time_charging_factory) chargemode: Chargemode = field(default_factory=chargemode_factory) diff --git a/packages/control/ev/ev.py b/packages/control/ev/ev.py index ae2d1aa02b..ded85562e8 100644 --- a/packages/control/ev/ev.py +++ b/packages/control/ev/ev.py @@ -76,6 +76,7 @@ class EvData: set: Set = field(default_factory=set_factory) charge_template: int = field(default=0, metadata={"topic": "charge_template"}) ev_template: int = field(default=0, metadata={"topic": "ev_template"}) + full_power: bool = False name: str = field(default="neues Fahrzeug", metadata={"topic": "name"}) tag_id: List[str] = field(default_factory=empty_list_factory, metadata={ "topic": "tag_id"}) diff --git a/packages/helpermodules/command.py b/packages/helpermodules/command.py index 1770d4ba89..f7fc630cd8 100644 --- a/packages/helpermodules/command.py +++ b/packages/helpermodules/command.py @@ -286,10 +286,10 @@ def setup_added_chargepoint(): chargepoint_config["id"] = new_id chargepoint_config["name"] = f'{chargepoint_config["name"]} {new_id}' try: - evu_counter = data.data.counter_all_data.get_id_evu_counter() - data.data.counter_all_data.hierarchy_add_item_below( + evu_counter = SubData.counter_all_data.get_id_evu_counter() + SubData.counter_all_data.hierarchy_add_item_below( new_id, ComponentType.CHARGEPOINT, evu_counter) - Pub().pub("openWB/set/counter/get/hierarchy", data.data.counter_all_data.data.get.hierarchy) + Pub().pub("openWB/set/counter/get/hierarchy", SubData.counter_all_data.data.get.hierarchy) setup_added_chargepoint() except (TypeError, IndexError): if chargepoint_config["type"] == 'internal_openwb' and SubData.general_data.data.extern: @@ -299,9 +299,9 @@ def setup_added_chargepoint(): "type": ComponentType.CHARGEPOINT.value, "children": [] }] + - data.data.counter_all_data.data.get.hierarchy) + SubData.counter_all_data.data.get.hierarchy) Pub().pub("openWB/set/counter/get/hierarchy", hierarchy) - data.data.counter_all_data.data.get.hierarchy = hierarchy + SubData.counter_all_data.data.get.hierarchy = hierarchy setup_added_chargepoint() else: pub_user_message(payload, connection_id, @@ -350,8 +350,8 @@ def removeChargepoint(self, connection_id: str, payload: dict) -> None: f'Die ID \'{payload["data"]["id"]}\' ist größer als die maximal vergebene ' f'ID \'{self.max_id_hierarchy}\'.', MessageType.ERROR) ProcessBrokerBranch(f'chargepoint/{payload["data"]["id"]}/').remove_topics() - data.data.counter_all_data.hierarchy_remove_item(payload["data"]["id"]) - Pub().pub("openWB/set/counter/get/hierarchy", data.data.counter_all_data.data.get.hierarchy) + SubData.counter_all_data.hierarchy_remove_item(payload["data"]["id"]) + Pub().pub("openWB/set/counter/get/hierarchy", SubData.counter_all_data.data.get.hierarchy) pub_user_message(payload, connection_id, f'Ladepunkt mit ID \'{payload["data"]["id"]}\' gelöscht.', MessageType.SUCCESS) @@ -709,6 +709,9 @@ def addVehicle(self, connection_id: str, payload: dict) -> None: self.addChargeTemplate("addVehicle", {}) if self.max_id_ev_template == -1: self.addEvTemplate("addVehicle", {}) + SubData.counter_all_data.loadmanagement_prios_add_item(new_id, ComponentType.VEHICLE) + Pub().pub("openWB/set/counter/get/loadmanagement_prios", + SubData.counter_all_data.data.get.loadmanagement_prios) pub_user_message(payload, connection_id, f'Neues EV mit ID \'{new_id}\' hinzugefügt.', MessageType.SUCCESS) def removeVehicle(self, connection_id: str, payload: dict) -> None: @@ -720,6 +723,9 @@ def removeVehicle(self, connection_id: str, payload: dict) -> None: if payload["data"]["id"] > 0: Pub().pub(f'openWB/vehicle/{payload["data"]["id"]}', "") ProcessBrokerBranch(f'vehicle/{payload["data"]["id"]}/').remove_topics() + SubData.counter_all_data.loadmanagement_prios_remove_item(payload["data"]["id"]) + Pub().pub("openWB/set/counter/get/loadmanagement_prios", + SubData.counter_all_data.data.get.loadmanagement_prios) pub_user_message( payload, connection_id, f'EV mit ID \'{payload["data"]["id"]}\' gelöscht.', MessageType.SUCCESS) diff --git a/packages/helpermodules/setdata.py b/packages/helpermodules/setdata.py index 0ded855015..b10bad20da 100644 --- a/packages/helpermodules/setdata.py +++ b/packages/helpermodules/setdata.py @@ -913,7 +913,8 @@ def process_counter_topic(self, msg: mqtt.MQTTMessage): "openWB/set/counter/set/daily_yield_home_consumption" in msg.topic or "openWB/set/counter/set/disengageable_smarthome_power" in msg.topic): self._validate_value(msg, float, [(0, float("inf"))]) - elif "openWB/set/counter/get/hierarchy" in msg.topic: + elif ("openWB/set/counter/get/hierarchy" in msg.topic or + "openWB/set/counter/get/loadmanagement_prios" in msg.topic): self._validate_value(msg, None) elif "openWB/set/counter/config/home_consumption_source_id" in msg.topic: self._validate_value(msg, int) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 15530c627d..88014a9f28 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -11,6 +11,7 @@ from typing import List, Optional from paho.mqtt.client import Client as MqttClient, MQTTMessage +from control.chargemode import Chargemode from control.limiting_value import LoadmanagementLimit import dataclass_utils @@ -57,7 +58,7 @@ class UpdateConfig: - DATASTORE_VERSION = 107 + DATASTORE_VERSION = 108 valid_topic = [ "^openWB/bat/config/bat_control_permitted$", @@ -174,6 +175,7 @@ class UpdateConfig: "^openWB/counter/config/consider_less_charging$", "^openWB/counter/config/home_consumption_source_id$", "^openWB/counter/get/hierarchy$", + "^openWB/counter/get/loadmanagement_prios$", "^openWB/counter/set/disengageable_smarthome_power$", "^openWB/counter/set/imported_home_consumption$", "^openWB/counter/set/invalid_home_consumption$", @@ -519,6 +521,7 @@ class UpdateConfig: ("openWB/chargepoint/get/power", 0), ("openWB/chargepoint/template/0", get_chargepoint_template_default()), ("openWB/counter/get/hierarchy", []), + ("openWB/counter/get/loadmanagement_prios", ["ev0"]), ("openWB/counter/config/consider_less_charging", counter_all.Config().consider_less_charging), ("openWB/counter/config/home_consumption_source_id", counter_all.Config().home_consumption_source_id), ("openWB/vehicle/0/name", "Standard-Fahrzeug"), @@ -2701,3 +2704,33 @@ def upgrade(topic: str, payload) -> None: return {topic: provider} self._loop_all_received_topics(upgrade) self._append_datastore_version(107) + + def upgrade_datastore_108(self) -> None: + def upgrade(topic: str, payload) -> None: + if re.search("openWB/ev/[0-9]+/name$", topic) is not None: + return {topic.replace("/name", "/full_power"): False} + self._loop_all_received_topics(upgrade) + + CHARGEMODES = ((Chargemode.SCHEDULED_CHARGING.value, True), + (Chargemode.SCHEDULED_CHARGING.value, False), + (Chargemode.INSTANT_CHARGING.value, True), + (Chargemode.INSTANT_CHARGING.value, False), + (Chargemode.ECO_CHARGING.value, True), + (Chargemode.PV_CHARGING.value, True), + (Chargemode.PV_CHARGING.value, False), + (Chargemode.STOP.value, True), + (Chargemode.STOP.value, False),) + + def upgrade2(topic: str, payload) -> None: + if re.search("openWB/ev/[0-9]+/charge_template", topic) is not None: + charge_template_id = decode_payload(payload) + charge_template = decode_payload( + self.all_received_topics[f"openWB/vehicle/template/charge_template/{charge_template_id}"]) + if charge_template["chargemode"]["selected"] == chargemoe and charge_template["prio"] == prio: + loadmanagement_prios.append({"type": "ev", "id": int(get_index(topic))}) + + loadmanagement_prios = [] + for chargemoe, prio in CHARGEMODES: + self._loop_all_received_topics(upgrade2) + self.__update_topic("openWB/counter/get/loadmanagement_prios", loadmanagement_prios) + self._append_datastore_version(108) diff --git a/packages/modules/common/component_type.py b/packages/modules/common/component_type.py index a759d3c533..a04436c153 100644 --- a/packages/modules/common/component_type.py +++ b/packages/modules/common/component_type.py @@ -6,11 +6,13 @@ class ComponentType(Enum): BACKUP_CLOUD = "backup_cloud" BAT = "bat" CHARGEPOINT = "cp" + CONSUMER = "consumer" COUNTER = "counter" FLEXIBLE_TARIFF = "dynamic_tariff" GRID_FEE = "grid_tariff" INVERTER = "inverter" IO = "io" + VEHICLE = "vehicle" def special_to_general_type_mapping(component_type: str) -> ComponentType: From 5295387d47fb4b3f6fcdaddb72737700f07f9c7f Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Mon, 15 Dec 2025 09:41:22 +0100 Subject: [PATCH 02/18] fix pytest --- packages/control/algorithm/common.py | 18 ++++++++---------- .../integration_test/instant_charging_test.py | 10 +++++----- packages/control/counter_all.py | 2 +- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/packages/control/algorithm/common.py b/packages/control/algorithm/common.py index f5e68d751f..e3f3a335a8 100644 --- a/packages/control/algorithm/common.py +++ b/packages/control/algorithm/common.py @@ -111,16 +111,14 @@ def available_current_for_cp(chargepoint: Chargepoint, available_current = float("inf") missing_current_cp = control_parameter.required_current - chargepoint.data.set.target_current - if chargepoint.data.set.charging_ev_data.data.full_power: - return missing_current_cp - else: - for i in range(0, 3): - if (control_parameter.required_currents[i] != 0 and - missing_currents[i] != available_currents[i]): - available_current = min(min(missing_current_cp, available_currents[i]/counts[i]), available_current) - if available_current == float("inf"): - available_current = missing_current_cp - return available_current + for i in range(0, 3): + shared_with = 1 if chargepoint.data.set.charging_ev_data.data.full_power else counts[i] + if (control_parameter.required_currents[i] != 0 and missing_currents[i] != available_currents[i]): + available_current = min(min(missing_current_cp, available_currents[i]/shared_with), available_current) + + if available_current == float("inf"): + available_current = missing_current_cp + return available_current def update_raw_data(preferenced_chargepoints: List[Chargepoint], diff --git a/packages/control/algorithm/integration_test/instant_charging_test.py b/packages/control/algorithm/integration_test/instant_charging_test.py index 3bda57f2c6..a6dc3e8f05 100644 --- a/packages/control/algorithm/integration_test/instant_charging_test.py +++ b/packages/control/algorithm/integration_test/instant_charging_test.py @@ -169,16 +169,16 @@ class ParamsControlParameter(ParamsExpectedSetCurrent, ParamsExpectedCounterSet) expected_current_cp3=16, expected_current_cp4=6, expected_current_cp5=10, - expected_raw_power_left=690, - expected_raw_currents_left_counter0=[1]*3, + expected_raw_power_left=0, + expected_raw_currents_left_counter0=[0]*3, expected_raw_currents_left_counter6=[0]*3), ParamsControlParameter(name="change submode cp4", submode_cp4=Chargemode.TIME_CHARGING, - expected_current_cp3=16, + expected_current_cp3=14, expected_current_cp4=8, expected_current_cp5=8, - expected_raw_power_left=2070, - expected_raw_currents_left_counter0=[3]*3, + expected_raw_power_left=1380, + expected_raw_currents_left_counter0=[2]*3, expected_raw_currents_left_counter6=[0]*3), ] diff --git a/packages/control/counter_all.py b/packages/control/counter_all.py index 62583db0e0..8369717f43 100644 --- a/packages/control/counter_all.py +++ b/packages/control/counter_all.py @@ -227,7 +227,7 @@ def get_chargepoints_of_counter(self, counter: str) -> List[str]: except KeyError: # Kein Ladepunkt unter dem Zähler pass - return self.connected_chargepoints + return reversed(self.connected_chargepoints) def _get_all_cp_connected_to_counter(self, child: Dict) -> None: """ Rekursive Funktion, die alle Ladepunkte ermittelt, die an den angegebenen Zähler angeschlossen sind. From b0035cdb6da55de3b0940a4d455e69720c9f9ea6 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Mon, 15 Dec 2025 10:36:11 +0100 Subject: [PATCH 03/18] fix pytest --- .../control/algorithm/filter_chargepoints.py | 2 +- .../algorithm/filter_chargepoints_test.py | 48 +++++++++++-------- .../algorithm/surplus_controlled_test.py | 24 ++++++++-- packages/control/counter_all.py | 2 +- packages/control/hierarchy_test.py | 2 +- 5 files changed, 51 insertions(+), 27 deletions(-) diff --git a/packages/control/algorithm/filter_chargepoints.py b/packages/control/algorithm/filter_chargepoints.py index ea53e8e50e..37f3dbd823 100644 --- a/packages/control/algorithm/filter_chargepoints.py +++ b/packages/control/algorithm/filter_chargepoints.py @@ -34,7 +34,7 @@ def _process_chargemodes(power_filter_func): (cp.data.control_parameter.submode == chargemode[1]) and cp not in valid and cp not in valid_chargemode): valid_chargemode.append(cp) - valid.extend(_get_preferenced_chargepoint(valid_chargemode)) + valid.extend(valid_chargemode) valid = [] if full_power_considered: diff --git a/packages/control/algorithm/filter_chargepoints_test.py b/packages/control/algorithm/filter_chargepoints_test.py index fe52d9047a..24f80c0adf 100644 --- a/packages/control/algorithm/filter_chargepoints_test.py +++ b/packages/control/algorithm/filter_chargepoints_test.py @@ -96,23 +96,27 @@ def mock_cp(cp: Chargepoint, num: int): @pytest.mark.parametrize( - "required_current_1, loadmanagement_prios, expected_valid_chargepoints", + "required_current_1, loadmanagement_prios, expected_cp_indices", [ - pytest.param(6, [{"type": "ev", "id": "1"}, {"type": "ev", "id": "2"}], - [mock_cp1, mock_cp2], id="fits mode"), - pytest.param(0, [{"type": "ev", "id": "1"}, {"type": "ev", "id": "2"}], - [mock_cp2], id="cp1 should not charge"), - pytest.param(6, [{"type": "ev", "id": "2"}, {"type": "ev", "id": "1"}], - [mock_cp2, mock_cp1], id="cp2 is prioritized") + pytest.param(6, [{"type": "ev", "id": 1}, {"type": "ev", "id": 2}], + [1, 2], id="fits mode"), + pytest.param(0, [{"type": "ev", "id": 1}, {"type": "ev", "id": 2}], + [2], id="cp1 should not charge"), + pytest.param(6, [{"type": "ev", "id": 2}, {"type": "ev", "id": 1}], + [2, 1], id="cp2 is prioritized") ]) def test_get_chargepoints_by_mode(required_current_1: int, loadmanagement_prios: List[Dict], - expected_valid_chargepoints): + expected_cp_indices, + mock_cp1, mock_cp2): # setup def setup_cp(cp: Chargepoint, required_current: float) -> Chargepoint: + cp.data.set.charging_ev_data = Ev(cp.num) + cp.data.config.ev = cp.num cp.data.control_parameter.required_current = required_current cp.data.control_parameter.chargemode = Chargemode.SCHEDULED_CHARGING cp.data.control_parameter.submode = Chargemode.INSTANT_CHARGING + cp.data.set.plug_time = 1 return cp data.data.cp_data = {"cp1": setup_cp(mock_cp1, required_current_1), "cp2": setup_cp(mock_cp2, 6)} @@ -121,29 +125,35 @@ def setup_cp(cp: Chargepoint, required_current: float) -> Chargepoint: # evaluation valid_chargepoints = filter_chargepoints.get_loadmanagement_prios( - (Chargemode.SCHEDULED_CHARGING, Chargemode.INSTANT_CHARGING)) + ((Chargemode.SCHEDULED_CHARGING, Chargemode.INSTANT_CHARGING),)) # assertion + cp_mapping = {1: mock_cp1, 2: mock_cp2} + expected_valid_chargepoints = [cp_mapping[i] for i in expected_cp_indices] assert valid_chargepoints == expected_valid_chargepoints @pytest.mark.parametrize( - "chargepoints_of_counter, chargepoints_by_mode, expected_chargepoints", + "chargepoints_of_counter, chargepoints_by_mode_indices, expected_cp_indices", [ - pytest.param(["cp1", "cp2"], [mock_cp1, mock_cp2], [mock_cp1, mock_cp2], id="match all"), - pytest.param(["cp1", "cp2"], [mock_cp1], [mock_cp1], id="match by mode"), - pytest.param(["cp2"], [mock_cp1, mock_cp2], [mock_cp2], id="match by counter"), - pytest.param(["cp1"], [mock_cp2], [], id="match none") + pytest.param(["cp1", "cp2"], [1, 2], [1, 2], id="match all"), + pytest.param(["cp1", "cp2"], [1], [1], id="match by mode"), + pytest.param(["cp2"], [1, 2], [2], id="match by counter"), + pytest.param(["cp1"], [2], [], id="match none") ]) def test_get_chargepoints_by_mode_and_counter(chargepoints_of_counter: List[str], - chargepoints_by_mode: List[Chargepoint], - expected_chargepoints: List[Chargepoint], - monkeypatch): + chargepoints_by_mode_indices: List[int], + expected_cp_indices: List[int], + monkeypatch, mock_cp1, mock_cp2): # setup + cp_mapping = {1: mock_cp1, 2: mock_cp2} + chargepoints_by_mode = [cp_mapping[i] for i in chargepoints_by_mode_indices] + expected_chargepoints = [cp_mapping[i] for i in expected_cp_indices] + 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_loadmanagement_prios_mock = Mock(return_value=chargepoints_by_mode) + monkeypatch.setattr(filter_chargepoints, "get_loadmanagement_prios", get_loadmanagement_prios_mock) data.data.counter_all_data = CounterAll() # evaluation diff --git a/packages/control/algorithm/surplus_controlled_test.py b/packages/control/algorithm/surplus_controlled_test.py index 46de9af168..3effda5feb 100644 --- a/packages/control/algorithm/surplus_controlled_test.py +++ b/packages/control/algorithm/surplus_controlled_test.py @@ -12,6 +12,7 @@ from control.chargepoint.chargepoint_data import Get, Set from control.chargepoint.chargepoint_template import CpTemplate from control.chargepoint.control_parameter import ControlParameter +from control.counter_all import CounterAll from control.ev.ev import Ev @@ -30,6 +31,12 @@ def mock_cp3() -> Chargepoint: return Chargepoint(3, None) +@pytest.fixture(autouse=True) +def mock_data() -> None: + data.data_init(Mock()) + data.data.counter_all_data = CounterAll() + + @pytest.mark.parametrize("feed_in_limit_1, feed_in_limit_2, feed_in_limit_3, expected_sorted", [pytest.param(True, True, True, ([mock_cp1, mock_cp2, mock_cp3], [])), pytest.param(True, False, True, ([mock_cp1, mock_cp3], [mock_cp2])), @@ -129,27 +136,34 @@ def test_add_unused_evse_current(evse_current: float, @pytest.mark.parametrize( - "submode_1, submode_2, expected_chargepoints", + "submode_1, submode_2, expected_cp_indices", [ - pytest.param(Chargemode.PV_CHARGING, Chargemode.PV_CHARGING, [mock_cp1, mock_cp2]), - pytest.param(Chargemode.INSTANT_CHARGING, Chargemode.PV_CHARGING, [mock_cp2]), + pytest.param(Chargemode.PV_CHARGING, Chargemode.PV_CHARGING, [1, 2]), + pytest.param(Chargemode.INSTANT_CHARGING, Chargemode.PV_CHARGING, [2]), pytest.param(Chargemode.INSTANT_CHARGING, Chargemode.INSTANT_CHARGING, []), ]) def test_get_chargepoints_submode_pv_charging(submode_1: Chargemode, submode_2: Chargemode, - expected_chargepoints: List[Chargepoint]): + expected_cp_indices: List[int], + mock_cp1, mock_cp2, mock_data): # setup def setup_cp(cp: Chargepoint, submode: str) -> Chargepoint: - cp.data.set.charging_ev_data = Ev(0) + cp.data.set.charging_ev_data = Ev(cp.num) + cp.data.config.ev = cp.num cp.data.control_parameter.chargemode = Chargemode.PV_CHARGING cp.data.control_parameter.submode = submode cp.data.control_parameter.required_current = 6 + cp.data.set.plug_time = 1 return cp data.data.cp_data = {"cp1": setup_cp(mock_cp1, submode_1), "cp2": setup_cp(mock_cp2, submode_2)} + data.data.counter_all_data.data.get.loadmanagement_prios = [ + {"type": "ev", "id": 1}, {"type": "ev", "id": 2}] # evaluation chargepoints = get_loadmanagement_prios(CONSIDERED_CHARGE_MODES_PV_ONLY) # assertion + cp_mapping = {1: mock_cp1, 2: mock_cp2} + expected_chargepoints = [cp_mapping[i] for i in expected_cp_indices] assert chargepoints == expected_chargepoints diff --git a/packages/control/counter_all.py b/packages/control/counter_all.py index 8369717f43..85a626d9c5 100644 --- a/packages/control/counter_all.py +++ b/packages/control/counter_all.py @@ -227,7 +227,7 @@ def get_chargepoints_of_counter(self, counter: str) -> List[str]: except KeyError: # Kein Ladepunkt unter dem Zähler pass - return reversed(self.connected_chargepoints) + return list(reversed(self.connected_chargepoints)) def _get_all_cp_connected_to_counter(self, child: Dict) -> None: """ Rekursive Funktion, die alle Ladepunkte ermittelt, die an den angegebenen Zähler angeschlossen sind. diff --git a/packages/control/hierarchy_test.py b/packages/control/hierarchy_test.py index 297a9d7a3b..a135709452 100644 --- a/packages/control/hierarchy_test.py +++ b/packages/control/hierarchy_test.py @@ -142,7 +142,7 @@ def test_delete_discard_children(params: ParamsItem): cases_get_chargepoints_of_counter = [ - ParamsItem("get_chargepoints_of_counter", hierarchy_cp(), "counter2", expected_return=["cp3", "cp5", "cp6"]), + ParamsItem("get_chargepoints_of_counter", hierarchy_cp(), "counter2", expected_return=["cp6", "cp5", "cp3"]), ParamsItem("get_chargepoints_of_counter", hierarchy_two_level(), "counter0", expected_return=["cp2"]) ] From 545a5cd388a14b052b9399a6017a770c29bc835d Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Wed, 14 Jan 2026 11:06:31 +0100 Subject: [PATCH 04/18] fix --- packages/helpermodules/update_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 88014a9f28..b68e0576c3 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -521,7 +521,7 @@ class UpdateConfig: ("openWB/chargepoint/get/power", 0), ("openWB/chargepoint/template/0", get_chargepoint_template_default()), ("openWB/counter/get/hierarchy", []), - ("openWB/counter/get/loadmanagement_prios", ["ev0"]), + ("openWB/counter/get/loadmanagement_prios", [{"id": 0, "type": "vehicle"}]), ("openWB/counter/config/consider_less_charging", counter_all.Config().consider_less_charging), ("openWB/counter/config/home_consumption_source_id", counter_all.Config().home_consumption_source_id), ("openWB/vehicle/0/name", "Standard-Fahrzeug"), From a0dd27f4c559b83738b3824b7ea7935ca60c9d85 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Wed, 28 Jan 2026 16:07:36 +0100 Subject: [PATCH 05/18] max surplus --- packages/control/algorithm/common.py | 2 +- .../control/algorithm/filter_chargepoints.py | 12 +++++----- .../integration_test/instant_charging_test.py | 24 +++++++++---------- packages/control/algorithm/min_current.py | 2 +- packages/control/ev/ev.py | 2 +- packages/helpermodules/setdata.py | 2 ++ packages/helpermodules/update_config.py | 9 +++---- 7 files changed, 28 insertions(+), 25 deletions(-) diff --git a/packages/control/algorithm/common.py b/packages/control/algorithm/common.py index e3f3a335a8..d1321ecf83 100644 --- a/packages/control/algorithm/common.py +++ b/packages/control/algorithm/common.py @@ -112,7 +112,7 @@ def available_current_for_cp(chargepoint: Chargepoint, missing_current_cp = control_parameter.required_current - chargepoint.data.set.target_current for i in range(0, 3): - shared_with = 1 if chargepoint.data.set.charging_ev_data.data.full_power else counts[i] + shared_with = 1 if chargepoint.data.set.charging_ev_data.data.max_surplus else counts[i] if (control_parameter.required_currents[i] != 0 and missing_currents[i] != available_currents[i]): available_current = min(min(missing_current_cp, available_currents[i]/shared_with), available_current) diff --git a/packages/control/algorithm/filter_chargepoints.py b/packages/control/algorithm/filter_chargepoints.py index 37f3dbd823..f73f0dc454 100644 --- a/packages/control/algorithm/filter_chargepoints.py +++ b/packages/control/algorithm/filter_chargepoints.py @@ -10,17 +10,17 @@ def get_chargepoints_by_mode_and_counter(chargemodes: Tuple[Tuple[Optional[str], str]], counter: str, - full_power_considered: bool = True) -> List[Chargepoint]: + max_surplus_considered: bool = True) -> 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_loadmanagement_prios(chargemodes, full_power_considered) + cps_by_mode = get_loadmanagement_prios(chargemodes, max_surplus_considered) return list(filter(lambda cp: cp.num in cps_to_counter_ids, cps_by_mode)) # tested def get_loadmanagement_prios(chargemodes: Tuple[Tuple[Optional[str], str]], - full_power_considered: bool = True) -> List[Chargepoint]: + max_surplus_considered: bool = True) -> List[Chargepoint]: def _process_chargemodes(power_filter_func): for chargemode in chargemodes: valid_chargemode = [] @@ -37,9 +37,9 @@ def _process_chargemodes(power_filter_func): valid.extend(valid_chargemode) valid = [] - if full_power_considered: - _process_chargemodes(lambda cp: cp.data.set.charging_ev_data.data.full_power is True) - _process_chargemodes(lambda cp: cp.data.set.charging_ev_data.data.full_power is False) + if max_surplus_considered: + _process_chargemodes(lambda cp: cp.data.set.charging_ev_data.data.max_surplus is True) + _process_chargemodes(lambda cp: cp.data.set.charging_ev_data.data.max_surplus is False) else: _process_chargemodes(lambda cp: True) return valid diff --git a/packages/control/algorithm/integration_test/instant_charging_test.py b/packages/control/algorithm/integration_test/instant_charging_test.py index a6dc3e8f05..273ffda63c 100644 --- a/packages/control/algorithm/integration_test/instant_charging_test.py +++ b/packages/control/algorithm/integration_test/instant_charging_test.py @@ -143,19 +143,19 @@ def test_instant_charging_limit(params: ParamsLimit, all_cp_instant_charging_1p, @dataclass class ParamsControlParameter(ParamsExpectedSetCurrent, ParamsExpectedCounterSet): name: str = "" - full_power_cp3: bool = False + max_surplus_cp3: bool = False submode_cp3: Chargemode = Chargemode.INSTANT_CHARGING - full_power_cp4: bool = False + max_surplus_cp4: bool = False submode_cp4: Chargemode = Chargemode.INSTANT_CHARGING - full_power_cp5: bool = False + max_surplus_cp5: bool = False submode_cp5: Chargemode = Chargemode.INSTANT_CHARGING cases_control_parameter = [ ParamsControlParameter(name="lift prio cp3", - full_power_cp3=True, - full_power_cp4=False, - full_power_cp5=False, + max_surplus_cp3=True, + max_surplus_cp4=False, + max_surplus_cp5=False, expected_current_cp3=16, expected_current_cp4=8, expected_current_cp5=8, @@ -163,9 +163,9 @@ class ParamsControlParameter(ParamsExpectedSetCurrent, ParamsExpectedCounterSet) expected_raw_currents_left_counter0=[0]*3, expected_raw_currents_left_counter6=[0]*3), ParamsControlParameter(name="drop prio cp4", - full_power_cp3=True, - full_power_cp4=False, - full_power_cp5=True, + max_surplus_cp3=True, + max_surplus_cp4=False, + max_surplus_cp5=True, expected_current_cp3=16, expected_current_cp4=6, expected_current_cp5=10, @@ -187,11 +187,11 @@ class ParamsControlParameter(ParamsExpectedSetCurrent, ParamsExpectedCounterSet) def test_control_parameter_instant_charging(params: ParamsControlParameter, all_cp_instant_charging_3p, monkeypatch): # setup data.data.cp_data["cp3"].data.control_parameter.submode = params.submode_cp3 - data.data.cp_data["cp3"].data.set.charging_ev_data.data.full_power = params.full_power_cp3 + data.data.cp_data["cp3"].data.set.charging_ev_data.data.max_surplus = params.max_surplus_cp3 data.data.cp_data["cp4"].data.control_parameter.submode = params.submode_cp4 - data.data.cp_data["cp4"].data.set.charging_ev_data.data.full_power = params.full_power_cp4 + data.data.cp_data["cp4"].data.set.charging_ev_data.data.max_surplus = params.max_surplus_cp4 data.data.cp_data["cp5"].data.control_parameter.submode = params.submode_cp5 - data.data.cp_data["cp5"].data.set.charging_ev_data.data.full_power = params.full_power_cp5 + data.data.cp_data["cp5"].data.set.charging_ev_data.data.max_surplus = params.max_surplus_cp5 data.data.counter_data["counter0"].data.set.raw_power_left = 22080 data.data.counter_data["counter0"].data.set.raw_currents_left = [32]*3 data.data.counter_data["counter6"].data.set.raw_currents_left = [16]*3 diff --git a/packages/control/algorithm/min_current.py b/packages/control/algorithm/min_current.py index 248b695f72..753dbb32e7 100644 --- a/packages/control/algorithm/min_current.py +++ b/packages/control/algorithm/min_current.py @@ -19,7 +19,7 @@ def set_min_current(self) -> None: for counter in common.counter_generator(): preferenced_chargepoints = get_chargepoints_by_mode_and_counter(CONSIDERED_CHARGE_MODES_MIN_CURRENT, f"counter{counter.num}", - full_power_considered=False) + max_surplus_considered=False) if preferenced_chargepoints: log.info(f"Zähler {counter.num}, Verbraucher {preferenced_chargepoints}") common.update_raw_data(preferenced_chargepoints, diff_to_zero=True) diff --git a/packages/control/ev/ev.py b/packages/control/ev/ev.py index ded85562e8..3b767ce70b 100644 --- a/packages/control/ev/ev.py +++ b/packages/control/ev/ev.py @@ -76,7 +76,7 @@ class EvData: set: Set = field(default_factory=set_factory) charge_template: int = field(default=0, metadata={"topic": "charge_template"}) ev_template: int = field(default=0, metadata={"topic": "ev_template"}) - full_power: bool = False + max_surplus: bool = field(default=False, metadata={"topic": "max_surplus"}) name: str = field(default="neues Fahrzeug", metadata={"topic": "name"}) tag_id: List[str] = field(default_factory=empty_list_factory, metadata={ "topic": "tag_id"}) diff --git a/packages/helpermodules/setdata.py b/packages/helpermodules/setdata.py index b10bad20da..c2ef31750e 100644 --- a/packages/helpermodules/setdata.py +++ b/packages/helpermodules/setdata.py @@ -367,6 +367,8 @@ def process_vehicle_topic(self, msg: mqtt.MQTTMessage): self._validate_value(msg, str) elif "/info" in msg.topic: self._validate_value(msg, "json") + elif "/max_surplus" in msg.topic: + self._validate_value(msg, bool) elif "openWB/set/vehicle/set/vehicle_update_completed" in msg.topic: self._validate_value(msg, bool) elif "/set/soc_error_counter" in msg.topic: diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index b68e0576c3..0b2bd9b1ab 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -361,6 +361,7 @@ class UpdateConfig: "^openWB/vehicle/[0-9]+/ev_template$", "^openWB/vehicle/[0-9]+/name$", "^openWB/vehicle/[0-9]+/info$", + "^openWB/vehicle/[0-9]+/max_surplus$", "^openWB/vehicle/[0-9]+/soc_module/calculated_soc_state$", "^openWB/vehicle/[0-9]+/soc_module/config$", "^openWB/vehicle/[0-9]+/soc_module/general_config$", @@ -2708,7 +2709,7 @@ def upgrade(topic: str, payload) -> None: def upgrade_datastore_108(self) -> None: def upgrade(topic: str, payload) -> None: if re.search("openWB/ev/[0-9]+/name$", topic) is not None: - return {topic.replace("/name", "/full_power"): False} + return {topic.replace("/name", "/max_surplus"): False} self._loop_all_received_topics(upgrade) CHARGEMODES = ((Chargemode.SCHEDULED_CHARGING.value, True), @@ -2726,11 +2727,11 @@ def upgrade2(topic: str, payload) -> None: charge_template_id = decode_payload(payload) charge_template = decode_payload( self.all_received_topics[f"openWB/vehicle/template/charge_template/{charge_template_id}"]) - if charge_template["chargemode"]["selected"] == chargemoe and charge_template["prio"] == prio: - loadmanagement_prios.append({"type": "ev", "id": int(get_index(topic))}) + if charge_template["chargemode"]["selected"] == chargemode and charge_template["prio"] == prio: + loadmanagement_prios.append({"type": "vehicle", "id": int(get_index(topic))}) loadmanagement_prios = [] - for chargemoe, prio in CHARGEMODES: + for chargemode, prio in CHARGEMODES: self._loop_all_received_topics(upgrade2) self.__update_topic("openWB/counter/get/loadmanagement_prios", loadmanagement_prios) self._append_datastore_version(108) From 1c232c32342fa0305c155f4c5b6e27ec62969104 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Wed, 28 Jan 2026 16:46:13 +0100 Subject: [PATCH 06/18] rename --- packages/control/algorithm/common.py | 2 +- .../control/algorithm/filter_chargepoints.py | 12 +++++----- .../integration_test/instant_charging_test.py | 24 +++++++++---------- packages/control/algorithm/min_current.py | 2 +- packages/control/ev/ev.py | 2 +- packages/helpermodules/setdata.py | 2 +- packages/helpermodules/update_config.py | 4 ++-- 7 files changed, 24 insertions(+), 24 deletions(-) diff --git a/packages/control/algorithm/common.py b/packages/control/algorithm/common.py index d1321ecf83..e3f3a335a8 100644 --- a/packages/control/algorithm/common.py +++ b/packages/control/algorithm/common.py @@ -112,7 +112,7 @@ def available_current_for_cp(chargepoint: Chargepoint, missing_current_cp = control_parameter.required_current - chargepoint.data.set.target_current for i in range(0, 3): - shared_with = 1 if chargepoint.data.set.charging_ev_data.data.max_surplus else counts[i] + shared_with = 1 if chargepoint.data.set.charging_ev_data.data.full_power else counts[i] if (control_parameter.required_currents[i] != 0 and missing_currents[i] != available_currents[i]): available_current = min(min(missing_current_cp, available_currents[i]/shared_with), available_current) diff --git a/packages/control/algorithm/filter_chargepoints.py b/packages/control/algorithm/filter_chargepoints.py index f73f0dc454..37f3dbd823 100644 --- a/packages/control/algorithm/filter_chargepoints.py +++ b/packages/control/algorithm/filter_chargepoints.py @@ -10,17 +10,17 @@ def get_chargepoints_by_mode_and_counter(chargemodes: Tuple[Tuple[Optional[str], str]], counter: str, - max_surplus_considered: bool = True) -> List[Chargepoint]: + full_power_considered: bool = True) -> 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_loadmanagement_prios(chargemodes, max_surplus_considered) + cps_by_mode = get_loadmanagement_prios(chargemodes, full_power_considered) return list(filter(lambda cp: cp.num in cps_to_counter_ids, cps_by_mode)) # tested def get_loadmanagement_prios(chargemodes: Tuple[Tuple[Optional[str], str]], - max_surplus_considered: bool = True) -> List[Chargepoint]: + full_power_considered: bool = True) -> List[Chargepoint]: def _process_chargemodes(power_filter_func): for chargemode in chargemodes: valid_chargemode = [] @@ -37,9 +37,9 @@ def _process_chargemodes(power_filter_func): valid.extend(valid_chargemode) valid = [] - if max_surplus_considered: - _process_chargemodes(lambda cp: cp.data.set.charging_ev_data.data.max_surplus is True) - _process_chargemodes(lambda cp: cp.data.set.charging_ev_data.data.max_surplus is False) + if full_power_considered: + _process_chargemodes(lambda cp: cp.data.set.charging_ev_data.data.full_power is True) + _process_chargemodes(lambda cp: cp.data.set.charging_ev_data.data.full_power is False) else: _process_chargemodes(lambda cp: True) return valid diff --git a/packages/control/algorithm/integration_test/instant_charging_test.py b/packages/control/algorithm/integration_test/instant_charging_test.py index 273ffda63c..a6dc3e8f05 100644 --- a/packages/control/algorithm/integration_test/instant_charging_test.py +++ b/packages/control/algorithm/integration_test/instant_charging_test.py @@ -143,19 +143,19 @@ def test_instant_charging_limit(params: ParamsLimit, all_cp_instant_charging_1p, @dataclass class ParamsControlParameter(ParamsExpectedSetCurrent, ParamsExpectedCounterSet): name: str = "" - max_surplus_cp3: bool = False + full_power_cp3: bool = False submode_cp3: Chargemode = Chargemode.INSTANT_CHARGING - max_surplus_cp4: bool = False + full_power_cp4: bool = False submode_cp4: Chargemode = Chargemode.INSTANT_CHARGING - max_surplus_cp5: bool = False + full_power_cp5: bool = False submode_cp5: Chargemode = Chargemode.INSTANT_CHARGING cases_control_parameter = [ ParamsControlParameter(name="lift prio cp3", - max_surplus_cp3=True, - max_surplus_cp4=False, - max_surplus_cp5=False, + full_power_cp3=True, + full_power_cp4=False, + full_power_cp5=False, expected_current_cp3=16, expected_current_cp4=8, expected_current_cp5=8, @@ -163,9 +163,9 @@ class ParamsControlParameter(ParamsExpectedSetCurrent, ParamsExpectedCounterSet) expected_raw_currents_left_counter0=[0]*3, expected_raw_currents_left_counter6=[0]*3), ParamsControlParameter(name="drop prio cp4", - max_surplus_cp3=True, - max_surplus_cp4=False, - max_surplus_cp5=True, + full_power_cp3=True, + full_power_cp4=False, + full_power_cp5=True, expected_current_cp3=16, expected_current_cp4=6, expected_current_cp5=10, @@ -187,11 +187,11 @@ class ParamsControlParameter(ParamsExpectedSetCurrent, ParamsExpectedCounterSet) def test_control_parameter_instant_charging(params: ParamsControlParameter, all_cp_instant_charging_3p, monkeypatch): # setup data.data.cp_data["cp3"].data.control_parameter.submode = params.submode_cp3 - data.data.cp_data["cp3"].data.set.charging_ev_data.data.max_surplus = params.max_surplus_cp3 + data.data.cp_data["cp3"].data.set.charging_ev_data.data.full_power = params.full_power_cp3 data.data.cp_data["cp4"].data.control_parameter.submode = params.submode_cp4 - data.data.cp_data["cp4"].data.set.charging_ev_data.data.max_surplus = params.max_surplus_cp4 + data.data.cp_data["cp4"].data.set.charging_ev_data.data.full_power = params.full_power_cp4 data.data.cp_data["cp5"].data.control_parameter.submode = params.submode_cp5 - data.data.cp_data["cp5"].data.set.charging_ev_data.data.max_surplus = params.max_surplus_cp5 + data.data.cp_data["cp5"].data.set.charging_ev_data.data.full_power = params.full_power_cp5 data.data.counter_data["counter0"].data.set.raw_power_left = 22080 data.data.counter_data["counter0"].data.set.raw_currents_left = [32]*3 data.data.counter_data["counter6"].data.set.raw_currents_left = [16]*3 diff --git a/packages/control/algorithm/min_current.py b/packages/control/algorithm/min_current.py index 753dbb32e7..248b695f72 100644 --- a/packages/control/algorithm/min_current.py +++ b/packages/control/algorithm/min_current.py @@ -19,7 +19,7 @@ def set_min_current(self) -> None: for counter in common.counter_generator(): preferenced_chargepoints = get_chargepoints_by_mode_and_counter(CONSIDERED_CHARGE_MODES_MIN_CURRENT, f"counter{counter.num}", - max_surplus_considered=False) + full_power_considered=False) if preferenced_chargepoints: log.info(f"Zähler {counter.num}, Verbraucher {preferenced_chargepoints}") common.update_raw_data(preferenced_chargepoints, diff_to_zero=True) diff --git a/packages/control/ev/ev.py b/packages/control/ev/ev.py index 3b767ce70b..075b4adb52 100644 --- a/packages/control/ev/ev.py +++ b/packages/control/ev/ev.py @@ -76,7 +76,7 @@ class EvData: set: Set = field(default_factory=set_factory) charge_template: int = field(default=0, metadata={"topic": "charge_template"}) ev_template: int = field(default=0, metadata={"topic": "ev_template"}) - max_surplus: bool = field(default=False, metadata={"topic": "max_surplus"}) + full_power: bool = field(default=False, metadata={"topic": "full_power"}) name: str = field(default="neues Fahrzeug", metadata={"topic": "name"}) tag_id: List[str] = field(default_factory=empty_list_factory, metadata={ "topic": "tag_id"}) diff --git a/packages/helpermodules/setdata.py b/packages/helpermodules/setdata.py index c2ef31750e..e0682e9285 100644 --- a/packages/helpermodules/setdata.py +++ b/packages/helpermodules/setdata.py @@ -367,7 +367,7 @@ def process_vehicle_topic(self, msg: mqtt.MQTTMessage): self._validate_value(msg, str) elif "/info" in msg.topic: self._validate_value(msg, "json") - elif "/max_surplus" in msg.topic: + elif "/full_power" in msg.topic: self._validate_value(msg, bool) elif "openWB/set/vehicle/set/vehicle_update_completed" in msg.topic: self._validate_value(msg, bool) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 0b2bd9b1ab..38517b15dd 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -361,7 +361,7 @@ class UpdateConfig: "^openWB/vehicle/[0-9]+/ev_template$", "^openWB/vehicle/[0-9]+/name$", "^openWB/vehicle/[0-9]+/info$", - "^openWB/vehicle/[0-9]+/max_surplus$", + "^openWB/vehicle/[0-9]+/full_power$", "^openWB/vehicle/[0-9]+/soc_module/calculated_soc_state$", "^openWB/vehicle/[0-9]+/soc_module/config$", "^openWB/vehicle/[0-9]+/soc_module/general_config$", @@ -2709,7 +2709,7 @@ def upgrade(topic: str, payload) -> None: def upgrade_datastore_108(self) -> None: def upgrade(topic: str, payload) -> None: if re.search("openWB/ev/[0-9]+/name$", topic) is not None: - return {topic.replace("/name", "/max_surplus"): False} + return {topic.replace("/name", "/full_power"): False} self._loop_all_received_topics(upgrade) CHARGEMODES = ((Chargemode.SCHEDULED_CHARGING.value, True), From a5bb488e23074532bdc914b853d3753375f2cb07 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Wed, 28 Jan 2026 17:04:59 +0100 Subject: [PATCH 07/18] fix --- packages/helpermodules/update_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 38517b15dd..35d75d363f 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -2708,7 +2708,7 @@ def upgrade(topic: str, payload) -> None: def upgrade_datastore_108(self) -> None: def upgrade(topic: str, payload) -> None: - if re.search("openWB/ev/[0-9]+/name$", topic) is not None: + if re.search("openWB/vehicle/[0-9]+/name$", topic) is not None: return {topic.replace("/name", "/full_power"): False} self._loop_all_received_topics(upgrade) @@ -2723,7 +2723,7 @@ def upgrade(topic: str, payload) -> None: (Chargemode.STOP.value, False),) def upgrade2(topic: str, payload) -> None: - if re.search("openWB/ev/[0-9]+/charge_template", topic) is not None: + if re.search("openWB/vehicle/[0-9]+/charge_template", topic) is not None: charge_template_id = decode_payload(payload) charge_template = decode_payload( self.all_received_topics[f"openWB/vehicle/template/charge_template/{charge_template_id}"]) From 18d8627cf7ec01a65b2fcdfddd1e22fd6d3a0b73 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Thu, 29 Jan 2026 08:21:44 +0100 Subject: [PATCH 08/18] fix --- packages/control/algorithm/filter_chargepoints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/control/algorithm/filter_chargepoints.py b/packages/control/algorithm/filter_chargepoints.py index 37f3dbd823..179c170d14 100644 --- a/packages/control/algorithm/filter_chargepoints.py +++ b/packages/control/algorithm/filter_chargepoints.py @@ -25,7 +25,7 @@ def _process_chargemodes(power_filter_func): for chargemode in chargemodes: valid_chargemode = [] for item in data.data.counter_all_data.data.get.loadmanagement_prios: - if item["type"] == "ev": + if item["type"] == "vehicle": for cp in data.data.cp_data.values(): if item["id"] == cp.data.config.ev and power_filter_func(cp): if cp.data.control_parameter.required_current != 0: From d70cfeadd8999bdac56e98a9b31748b07f5f5d5f Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Thu, 29 Jan 2026 09:35:01 +0100 Subject: [PATCH 09/18] fix pytest --- packages/control/algorithm/filter_chargepoints_test.py | 6 +++--- packages/control/algorithm/integration_test/conftest.py | 2 +- .../algorithm/integration_test/instant_charging_test.py | 2 +- packages/control/algorithm/surplus_controlled_test.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/control/algorithm/filter_chargepoints_test.py b/packages/control/algorithm/filter_chargepoints_test.py index 24f80c0adf..e599d308a2 100644 --- a/packages/control/algorithm/filter_chargepoints_test.py +++ b/packages/control/algorithm/filter_chargepoints_test.py @@ -98,11 +98,11 @@ def mock_cp(cp: Chargepoint, num: int): @pytest.mark.parametrize( "required_current_1, loadmanagement_prios, expected_cp_indices", [ - pytest.param(6, [{"type": "ev", "id": 1}, {"type": "ev", "id": 2}], + pytest.param(6, [{"type": "vehicle", "id": 1}, {"type": "vehicle", "id": 2}], [1, 2], id="fits mode"), - pytest.param(0, [{"type": "ev", "id": 1}, {"type": "ev", "id": 2}], + pytest.param(0, [{"type": "vehicle", "id": 1}, {"type": "vehicle", "id": 2}], [2], id="cp1 should not charge"), - pytest.param(6, [{"type": "ev", "id": 2}, {"type": "ev", "id": 1}], + pytest.param(6, [{"type": "vehicle", "id": 2}, {"type": "vehicle", "id": 1}], [2, 1], id="cp2 is prioritized") ]) def test_get_chargepoints_by_mode(required_current_1: int, diff --git a/packages/control/algorithm/integration_test/conftest.py b/packages/control/algorithm/integration_test/conftest.py index 846a9da9e5..a19c3b56ca 100644 --- a/packages/control/algorithm/integration_test/conftest.py +++ b/packages/control/algorithm/integration_test/conftest.py @@ -50,7 +50,7 @@ def data_() -> None: data.data.counter_data["counter6"].data.config.max_total_power = 11000 data.data.counter_all_data = CounterAll() data.data.counter_all_data.data.get.hierarchy = NESTED_HIERARCHY - data.data.counter_all_data.data.get.loadmanagement_prios = [{"type": "ev", "id": 0}] + data.data.counter_all_data.data.get.loadmanagement_prios = [{"type": "vehicle", "id": 0}] data.data.counter_all_data.data.config.consider_less_charging = True data.data.io_actions = IoActions() diff --git a/packages/control/algorithm/integration_test/instant_charging_test.py b/packages/control/algorithm/integration_test/instant_charging_test.py index a6dc3e8f05..a1ea40cd40 100644 --- a/packages/control/algorithm/integration_test/instant_charging_test.py +++ b/packages/control/algorithm/integration_test/instant_charging_test.py @@ -47,7 +47,7 @@ def all_cp_instant_charging_3p(): data.data.cp_data[f"cp{i}"].data.set.charging_ev_data = Ev(i) data.data.cp_data[f"cp{i}"].data.config.ev = i data.data.counter_all_data.data.get.loadmanagement_prios = [ - {"type": "ev", "id": 3}, {"type": "ev", "id": 4}, {"type": "ev", "id": 5}] + {"type": "vehicle", "id": 3}, {"type": "vehicle", "id": 4}, {"type": "vehicle", "id": 5}] @dataclass diff --git a/packages/control/algorithm/surplus_controlled_test.py b/packages/control/algorithm/surplus_controlled_test.py index 3effda5feb..4c5a5cca13 100644 --- a/packages/control/algorithm/surplus_controlled_test.py +++ b/packages/control/algorithm/surplus_controlled_test.py @@ -158,7 +158,7 @@ def setup_cp(cp: Chargepoint, submode: str) -> Chargepoint: data.data.cp_data = {"cp1": setup_cp(mock_cp1, submode_1), "cp2": setup_cp(mock_cp2, submode_2)} data.data.counter_all_data.data.get.loadmanagement_prios = [ - {"type": "ev", "id": 1}, {"type": "ev", "id": 2}] + {"type": "vehicle", "id": 1}, {"type": "vehicle", "id": 2}] # evaluation chargepoints = get_loadmanagement_prios(CONSIDERED_CHARGE_MODES_PV_ONLY) From c51ec81321bbbf34ca35e305a5b68a5f7c384022 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 30 Jan 2026 11:32:04 +0100 Subject: [PATCH 10/18] review --- .../control/algorithm/additional_current.py | 13 ++-- packages/control/algorithm/algorithm.py | 55 ++++++++--------- packages/control/algorithm/bidi_charging.py | 7 ++- packages/control/algorithm/common.py | 11 ++-- .../control/algorithm/filter_chargepoints.py | 50 +++++++-------- .../algorithm/filter_chargepoints_test.py | 2 +- packages/control/algorithm/min_current.py | 13 ++-- packages/control/algorithm/no_current.py | 4 +- .../control/algorithm/surplus_controlled.py | 31 +++++++--- .../algorithm/surplus_controlled_test.py | 4 +- packages/control/bat_all.py | 4 +- packages/control/counter.py | 4 +- packages/control/counter_all.py | 61 ++++++++++++++++++- 13 files changed, 165 insertions(+), 94 deletions(-) diff --git a/packages/control/algorithm/additional_current.py b/packages/control/algorithm/additional_current.py index 66484ce98b..e95baf3d35 100644 --- a/packages/control/algorithm/additional_current.py +++ b/packages/control/algorithm/additional_current.py @@ -1,11 +1,12 @@ import logging +from typing import List from control.algorithm import common from control.algorithm.chargemodes import CONSIDERED_CHARGE_MODES_ADDITIONAL_CURRENT from control.limiting_value import LoadmanagementLimit from control.loadmanagement import Loadmanagement from control.chargepoint.chargepoint import Chargepoint -from control.algorithm.filter_chargepoints import (get_chargepoints_by_mode_and_counter, +from control.algorithm.filter_chargepoints import (get_chargepoints_by_mode_and_counter_and_lm_prio, get_preferenced_chargepoint_charging) log = logging.getLogger(__name__) @@ -16,12 +17,14 @@ class AdditionalCurrent: def __init__(self) -> None: pass - def set_additional_current(self) -> None: - common.reset_current_by_chargemode(CONSIDERED_CHARGE_MODES_ADDITIONAL_CURRENT) + def set_additional_current(self, cp_prio_group: List[Chargepoint]) -> None: + log.info("**Soll-Strom setzen**") + common.reset_current_to_target_current(cp_prio_group) + common.reset_current_by_chargemode(CONSIDERED_CHARGE_MODES_ADDITIONAL_CURRENT, cp_prio_group) for counter in common.counter_generator(): preferenced_chargepoints, preferenced_cps_without_set_current = get_preferenced_chargepoint_charging( - get_chargepoints_by_mode_and_counter(CONSIDERED_CHARGE_MODES_ADDITIONAL_CURRENT, - f"counter{counter.num}")) + get_chargepoints_by_mode_and_counter_and_lm_prio(CONSIDERED_CHARGE_MODES_ADDITIONAL_CURRENT, + f"counter{counter.num}", cp_prio_group)) if preferenced_chargepoints: common.update_raw_data(preferenced_chargepoints) log.info(f"Zähler {counter.num}, Verbraucher {preferenced_chargepoints}") diff --git a/packages/control/algorithm/algorithm.py b/packages/control/algorithm/algorithm.py index 73656d2524..4f07f559f5 100644 --- a/packages/control/algorithm/algorithm.py +++ b/packages/control/algorithm/algorithm.py @@ -1,6 +1,6 @@ import logging +from typing import List -from control import counter from control import data from control.algorithm import common from control.algorithm.additional_current import AdditionalCurrent @@ -8,6 +8,7 @@ from control.algorithm.min_current import MinCurrent from control.algorithm.no_current import NoCurrent from control.algorithm.surplus_controlled import SurplusControlled +from control.chargepoint.chargepoint import Chargepoint log = logging.getLogger(__name__) @@ -23,39 +24,35 @@ def __init__(self): def calc_current(self) -> None: """ Einstiegspunkt in den Regel-Algorithmus """ - try: - log.info("# Algorithmus") - self.evu_counter = data.data.counter_all_data.get_evu_counter() - self._check_auto_phase_switch_delay() - self.surplus_controlled.check_submode_pv_charging() - common.reset_current() - log.info("**Mindestrom setzen**") - self.min_current.set_min_current() - log.info("**Soll-Strom setzen**") - common.reset_current_to_target_current() - self.additional_current.set_additional_current() - self.surplus_controlled.set_required_current_to_max() - log.info("**PV-geführten Strom setzen**") - counter.limit_raw_power_left_to_surplus(self.evu_counter.calc_raw_surplus()) - if self.evu_counter.data.set.surplus_power_left > 0: - common.reset_current_to_target_current() - self.surplus_controlled.set_surplus_current() - else: - log.info("Keine Leistung für PV-geführtes Laden übrig.") - log.info("**Bidi-(Ent-)Lade-Strom setzen**") - counter.set_raw_surplus_power_left() - self.bidi.set_bidi() - self.no_current.set_no_current() - self.no_current.set_none_current() - except Exception: - log.exception("Fehler im Algorithmus-Modul") + log.info("# Algorithmus") + common.reset_current() + for next_low_power_group, next_full_power_group in data.data.counter_all_data.prio_groups_generator(): + try: + if next_low_power_group is not None: + self._check_auto_phase_switch_delay(next_low_power_group) + self.surplus_controlled.check_submode_pv_charging(next_low_power_group) + self.min_current.set_min_current(next_low_power_group) + if next_full_power_group is not None: + self._check_auto_phase_switch_delay(next_full_power_group) + self.surplus_controlled.check_submode_pv_charging(next_full_power_group) + self.min_current.set_min_current(next_full_power_group) + self.additional_current.set_additional_current(next_full_power_group) + self.surplus_controlled.set_surplus_current(next_full_power_group) + if next_low_power_group is not None: + self.additional_current.set_additional_current(next_low_power_group) + self.surplus_controlled.set_surplus_current(next_low_power_group) + except Exception: + log.exception("Fehler im Algorithmus-Modul") + self.bidi.set_bidi() + self.no_current.set_no_current() + self.no_current.set_none_current() - def _check_auto_phase_switch_delay(self) -> None: + def _check_auto_phase_switch_delay(self, cps: List[Chargepoint]) -> None: """ geht alle LP durch und prüft, ob eine Ladung aktiv ist, ob automatische Phasenumschaltung möglich ist und ob ob ein Timer gestartet oder gestoppt werden muss oder ob ein Timer abgelaufen ist. """ - for cp in data.data.cp_data.values(): + for cp in cps: try: if cp.data.control_parameter.required_current != 0: charging_ev = cp.data.set.charging_ev_data diff --git a/packages/control/algorithm/bidi_charging.py b/packages/control/algorithm/bidi_charging.py index 2e30d33724..bba6fa8536 100644 --- a/packages/control/algorithm/bidi_charging.py +++ b/packages/control/algorithm/bidi_charging.py @@ -1,7 +1,8 @@ import logging from control import data from control.algorithm.chargemodes import CONSIDERED_CHARGE_MODES_BIDI_DISCHARGE -from control.algorithm.filter_chargepoints import get_loadmanagement_prios +from control.algorithm.filter_chargepoints import get_chargepoints_by_mode +from control.counter import set_raw_surplus_power_left from helpermodules.phase_handling import voltages_mean log = logging.getLogger(__name__) @@ -12,10 +13,12 @@ def __init__(self): pass def set_bidi(self): + log.info("**Bidi-(Ent-)Lade-Strom setzen**") + set_raw_surplus_power_left() grid_counter = data.data.counter_all_data.get_evu_counter() log.debug(f"Nullpunktanpassung {grid_counter.data.set.surplus_power_left}W") zero_point_adjustment = grid_counter - preferenced_cps = get_loadmanagement_prios(CONSIDERED_CHARGE_MODES_BIDI_DISCHARGE) + preferenced_cps = get_chargepoints_by_mode(CONSIDERED_CHARGE_MODES_BIDI_DISCHARGE) if preferenced_cps: log.info(f"Verbraucher {preferenced_cps}") while len(preferenced_cps): diff --git a/packages/control/algorithm/common.py b/packages/control/algorithm/common.py index e3f3a335a8..e269b5e29b 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_loadmanagement_prios +from control.algorithm.filter_chargepoints import get_chargepoints_by_mode_and_lm_prio from control.algorithm.utils import get_medium_charging_current from control.chargepoint.chargepoint import Chargepoint from control.counter import Counter @@ -27,8 +27,9 @@ def reset_current(): log.exception(f"Fehler im Algorithmus-Modul für Ladepunkt{cp.num}") -def reset_current_by_chargemode(chargemodes: Tuple[Tuple[Optional[str], str]]) -> None: - for cp in get_loadmanagement_prios(chargemodes): +def reset_current_by_chargemode(chargemodes: Tuple[Tuple[Optional[str], str]], + cp_prio_group: List[Chargepoint]) -> None: + for cp in get_chargepoints_by_mode_and_lm_prio(chargemodes, cp_prio_group): cp.data.set.current = None @@ -191,11 +192,11 @@ def get_missing_currents_left(preferenced_chargepoints: List[Chargepoint]) -> Tu return missing_currents, counts -def reset_current_to_target_current(): +def reset_current_to_target_current(cp_prio_group: List[Chargepoint]) -> None: """target_current enthält die gesetzte Stromstärke der vorherigen Stufe. Notwendig, um zB bei der Mindeststromstärke erkennen zu können, ob diese ein vom LM begrenzter Strom aus Stufe 2 oder der Mindeststrom aus Stufe 1 ist.""" - for cp in data.data.cp_data.values(): + for cp in cp_prio_group: try: cp.data.set.target_current = cp.data.set.current except Exception: diff --git a/packages/control/algorithm/filter_chargepoints.py b/packages/control/algorithm/filter_chargepoints.py index 179c170d14..692b5ef100 100644 --- a/packages/control/algorithm/filter_chargepoints.py +++ b/packages/control/algorithm/filter_chargepoints.py @@ -8,41 +8,35 @@ log = logging.getLogger(__name__) -def get_chargepoints_by_mode_and_counter(chargemodes: Tuple[Tuple[Optional[str], str]], - counter: str, - full_power_considered: bool = True) -> List[Chargepoint]: +def get_chargepoints_by_mode_and_counter_and_lm_prio(chargemodes: Tuple[Tuple[Optional[str], str]], + counter: str, + prio_group: List[Chargepoint]) -> 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_loadmanagement_prios(chargemodes, full_power_considered) - return list(filter(lambda cp: cp.num in cps_to_counter_ids, cps_by_mode)) + cps_by_mode = get_chargepoints_by_mode(chargemodes) + return [ + cp for cp in prio_group + if cp in cps_by_mode and cp.num in cps_to_counter_ids + ] # tested -def get_loadmanagement_prios(chargemodes: Tuple[Tuple[Optional[str], str]], - full_power_considered: bool = True) -> List[Chargepoint]: - def _process_chargemodes(power_filter_func): - for chargemode in chargemodes: - valid_chargemode = [] - for item in data.data.counter_all_data.data.get.loadmanagement_prios: - if item["type"] == "vehicle": - for cp in data.data.cp_data.values(): - if item["id"] == cp.data.config.ev and power_filter_func(cp): - if cp.data.control_parameter.required_current != 0: - if ((cp.data.control_parameter.chargemode == chargemode[0] or - chargemode[0] is None) and - (cp.data.control_parameter.submode == chargemode[1]) and - cp not in valid and cp not in valid_chargemode): - valid_chargemode.append(cp) - valid.extend(valid_chargemode) +def get_chargepoints_by_mode(chargemodes: Tuple[Tuple[Optional[str], str]]) -> List[Chargepoint]: + cps = [] + for chargemode in chargemodes: + for cp in data.data.cp_data.values(): + if (cp.data.control_parameter.required_current != 0 and + (cp.data.control_parameter.chargemode == chargemode[0] or chargemode[0] is None) and + (cp.data.control_parameter.submode == chargemode[1])): + cps.append(cp) + return cps - valid = [] - if full_power_considered: - _process_chargemodes(lambda cp: cp.data.set.charging_ev_data.data.full_power is True) - _process_chargemodes(lambda cp: cp.data.set.charging_ev_data.data.full_power is False) - else: - _process_chargemodes(lambda cp: True) - return valid + +def get_chargepoints_by_mode_and_lm_prio(chargemodes: Tuple[Tuple[Optional[str], str]], + prio_group: List[Chargepoint]) -> List[Chargepoint]: + cps_by_mode = get_chargepoints_by_mode(chargemodes) + return list(filter(lambda cp: cp.num in cps_by_mode, prio_group)) def get_preferenced_chargepoint_charging( diff --git a/packages/control/algorithm/filter_chargepoints_test.py b/packages/control/algorithm/filter_chargepoints_test.py index e599d308a2..a34c5c641d 100644 --- a/packages/control/algorithm/filter_chargepoints_test.py +++ b/packages/control/algorithm/filter_chargepoints_test.py @@ -124,7 +124,7 @@ def setup_cp(cp: Chargepoint, required_current: float) -> Chargepoint: data.data.counter_all_data.data.get.loadmanagement_prios = loadmanagement_prios # evaluation - valid_chargepoints = filter_chargepoints.get_loadmanagement_prios( + valid_chargepoints = filter_chargepoints.get_chargepoints_by_mode( ((Chargemode.SCHEDULED_CHARGING, Chargemode.INSTANT_CHARGING),)) # assertion diff --git a/packages/control/algorithm/min_current.py b/packages/control/algorithm/min_current.py index 248b695f72..de6851990d 100644 --- a/packages/control/algorithm/min_current.py +++ b/packages/control/algorithm/min_current.py @@ -5,7 +5,7 @@ from control.algorithm.chargemodes import CONSIDERED_CHARGE_MODES_MIN_CURRENT, CONSIDERED_CHARGE_MODES_PV_ONLY from control.chargepoint.chargepoint_state import ChargepointState from control.loadmanagement import Loadmanagement -from control.algorithm.filter_chargepoints import get_chargepoints_by_mode_and_counter +from control.algorithm.filter_chargepoints import get_chargepoints_by_mode_and_counter_and_lm_prio log = logging.getLogger(__name__) @@ -15,11 +15,14 @@ class MinCurrent: def __init__(self) -> None: pass - def set_min_current(self) -> None: + def set_min_current(self, cp_prio_group) -> None: + log.info("**Mindestrom setzen**") + common.reset_current_to_target_current(cp_prio_group) for counter in common.counter_generator(): - preferenced_chargepoints = get_chargepoints_by_mode_and_counter(CONSIDERED_CHARGE_MODES_MIN_CURRENT, - f"counter{counter.num}", - full_power_considered=False) + preferenced_chargepoints = get_chargepoints_by_mode_and_counter_and_lm_prio( + CONSIDERED_CHARGE_MODES_MIN_CURRENT, + f"counter{counter.num}", + cp_prio_group) if preferenced_chargepoints: log.info(f"Zähler {counter.num}, Verbraucher {preferenced_chargepoints}") common.update_raw_data(preferenced_chargepoints, diff_to_zero=True) diff --git a/packages/control/algorithm/no_current.py b/packages/control/algorithm/no_current.py index ef1c217216..7961f755bb 100644 --- a/packages/control/algorithm/no_current.py +++ b/packages/control/algorithm/no_current.py @@ -2,7 +2,7 @@ from control import data from control.algorithm.chargemodes import CONSIDERED_CHARGE_MODES_NO_CURRENT -from control.algorithm.filter_chargepoints import get_loadmanagement_prios +from control.algorithm.filter_chargepoints import get_chargepoints_by_mode log = logging.getLogger(__name__) @@ -12,7 +12,7 @@ def __init__(self) -> None: pass def set_no_current(self) -> None: - chargepoints = get_loadmanagement_prios(CONSIDERED_CHARGE_MODES_NO_CURRENT) + chargepoints = get_chargepoints_by_mode(CONSIDERED_CHARGE_MODES_NO_CURRENT) for cp in chargepoints: cp.data.set.current = 0 diff --git a/packages/control/algorithm/surplus_controlled.py b/packages/control/algorithm/surplus_controlled.py index db5827f4aa..cf64f17c34 100644 --- a/packages/control/algorithm/surplus_controlled.py +++ b/packages/control/algorithm/surplus_controlled.py @@ -1,11 +1,13 @@ import logging from typing import List, Optional, Tuple +from control.counter import limit_raw_power_left_to_surplus from control import data 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_mode_and_counter, get_loadmanagement_prios, +from control.algorithm.filter_chargepoints import (get_chargepoints_by_mode_and_counter_and_lm_prio, + get_chargepoints_by_mode_and_lm_prio, get_preferenced_chargepoint_charging) from control.algorithm.utils import get_medium_charging_current from control.chargepoint.charging_type import ChargingType @@ -25,11 +27,20 @@ class SurplusControlled: def __init__(self) -> None: pass - def set_surplus_current(self) -> None: - common.reset_current_by_chargemode(CONSIDERED_CHARGE_MODES_SURPLUS) + def set_surplus_current(self, cp_prio_group: List[Chargepoint]) -> None: + self.set_required_current_to_max(cp_prio_group) + evu_counter = data.data.counter_all_data.get_evu_counter() + limit_raw_power_left_to_surplus(evu_counter.calc_raw_surplus()) + if evu_counter.data.set.surplus_power_left < 0: + log.info("Keine Leistung für PV-geführtes Laden übrig.") + return + log.info("**PV-geführten Strom setzen**") + common.reset_current_to_target_current(cp_prio_group) + common.reset_current_by_chargemode(CONSIDERED_CHARGE_MODES_SURPLUS, cp_prio_group) for counter in common.counter_generator(): preferenced_chargepoints, preferenced_cps_without_set_current = get_preferenced_chargepoint_charging( - get_chargepoints_by_mode_and_counter(CONSIDERED_CHARGE_MODES_SURPLUS, f"counter{counter.num}")) + get_chargepoints_by_mode_and_counter_and_lm_prio(CONSIDERED_CHARGE_MODES_SURPLUS, + f"counter{counter.num}", cp_prio_group)) cp_with_feed_in, cp_without_feed_in = self.filter_by_feed_in_limit(preferenced_chargepoints) if cp_without_feed_in: self._set(cp_without_feed_in, 0, counter) @@ -39,7 +50,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_loadmanagement_prios(CONSIDERED_CHARGE_MODES_SURPLUS): + for cp in get_chargepoints_by_mode_and_lm_prio(CONSIDERED_CHARGE_MODES_SURPLUS, cp_prio_group): if cp.data.control_parameter.state in CHARGING_STATES: self._fix_deviating_evse_current(cp) @@ -122,10 +133,10 @@ def _fix_deviating_evse_current(self, chargepoint: Chargepoint) -> float: log.debug(f"Ungenutzten Soll-Strom aufschlagen ergibt {current}A.") chargepoint.data.set.current = current - def check_submode_pv_charging(self) -> None: + def check_submode_pv_charging(self, cp_prio_group: List[Chargepoint]) -> None: evu_counter = data.data.counter_all_data.get_evu_counter() - for cp in get_loadmanagement_prios(CONSIDERED_CHARGE_MODES_PV_ONLY): + for cp in get_chargepoints_by_mode_and_lm_prio(CONSIDERED_CHARGE_MODES_PV_ONLY, cp_prio_group): try: def phase_switch_necessary() -> bool: return (cp.cp_state_hw_support_phase_switch() and @@ -156,9 +167,9 @@ def phase_switch_necessary() -> bool: except Exception: log.exception(f"Fehler in der PV-gesteuerten Ladung bei {cp.num}") - def set_required_current_to_max(self) -> None: - for cp in get_loadmanagement_prios(CONSIDERED_CHARGE_MODES_SURPLUS + - CONSIDERED_CHARGE_MODES_BIDI_DISCHARGE): + def set_required_current_to_max(self, cp_prio_group: List[Chargepoint]) -> None: + for cp in get_chargepoints_by_mode_and_lm_prio(CONSIDERED_CHARGE_MODES_SURPLUS + + CONSIDERED_CHARGE_MODES_BIDI_DISCHARGE, cp_prio_group): 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 4c5a5cca13..7a3521b746 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_loadmanagement_prios +from control.algorithm.filter_chargepoints import get_chargepoints_by_mode from control.algorithm.surplus_controlled import (CONSIDERED_CHARGE_MODES_PV_ONLY, SurplusControlled, limit_adjust_current) from control.chargemode import Chargemode @@ -161,7 +161,7 @@ def setup_cp(cp: Chargepoint, submode: str) -> Chargepoint: {"type": "vehicle", "id": 1}, {"type": "vehicle", "id": 2}] # evaluation - chargepoints = get_loadmanagement_prios(CONSIDERED_CHARGE_MODES_PV_ONLY) + chargepoints = get_chargepoints_by_mode(CONSIDERED_CHARGE_MODES_PV_ONLY) # assertion cp_mapping = {1: mock_cp1, 2: mock_cp2} diff --git a/packages/control/bat_all.py b/packages/control/bat_all.py index fbaadb4520..2df5cd023c 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_loadmanagement_prios +from control.algorithm.filter_chargepoints import get_chargepoints_by_mode from control.pv import Pv from helpermodules.constants import NO_ERROR from modules.common.abstract_device import AbstractDevice @@ -316,7 +316,7 @@ def get_power_limit(self): if self.data.config.bat_control_permitted is False: self.data.set.power_limit = None else: - chargepoint_by_chargemodes = get_loadmanagement_prios(CONSIDERED_CHARGE_MODES_CHARGING) + chargepoint_by_chargemodes = get_chargepoints_by_mode(CONSIDERED_CHARGE_MODES_CHARGING) # Falls aktive Steuerung an und Fahrzeuge laden und kein Überschuss im System ist, # dann Speicherleistung begrenzen. if (self.data.config.power_limit_mode != BatPowerLimitMode.NO_LIMIT.value and diff --git a/packages/control/counter.py b/packages/control/counter.py index f3789f067b..ff166e0040 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_loadmanagement_prios +from control.algorithm.filter_chargepoints import get_chargepoints_by_mode from control.algorithm.utils import get_medium_charging_current from control.chargemode import Chargemode from control.chargepoint.chargepoint import Chargepoint @@ -519,7 +519,7 @@ def set_raw_surplus_power_left() -> None: """ grid_counter = data.data.counter_all_data.get_evu_counter() bidi_power = 0 - chargepoint_by_chargemodes = get_loadmanagement_prios(CONSIDERED_CHARGE_MODES_BIDI_DISCHARGE) + chargepoint_by_chargemodes = get_chargepoints_by_mode(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/counter_all.py b/packages/control/counter_all.py index 85a626d9c5..d2eeec3dff 100644 --- a/packages/control/counter_all.py +++ b/packages/control/counter_all.py @@ -4,9 +4,10 @@ from dataclasses import dataclass, field import logging import re -from typing import Callable, Dict, List, Optional, Tuple, Union +from typing import Callable, Dict, Generator, List, Optional, Tuple, Union from control import data +from control.chargepoint.chargepoint import Chargepoint from control.counter import Counter from dataclass_utils.factories import empty_list_factory from helpermodules.messaging import MessageType, pub_system_message @@ -498,6 +499,64 @@ def loadmanagement_prios_remove_item(self, id: int) -> None: else: raise IndexError(f"Element {id} konnte nicht in den Lastmanagement-Prioritäten gefunden werden.") + def _get_prio_groups(self) -> List[Dict]: + groups = [] + current_group = [] + + for item in self.data.get.loadmanagement_prios: + if data.data.ev_data[f'ev{item["id"]}'].data.full_power: + if current_group: + groups.append({"items": current_group.copy(), "is_full_power_group": False}) + current_group.clear() + groups.append({"items": [item], "is_full_power_group": True}) + else: + current_group.append(item) + + if current_group: + groups.append({"items": current_group.copy(), "is_full_power_group": False}) + return groups + + def _convert_to_cp_group(self, group: Optional[Dict]) -> Optional[List[Counter]]: + if group is None: + return None + cp_group = [] + for item in group["items"]: + for cp in data.data.cp_data.values(): + if cp.data.config.ev == item["id"]: + cp_group.append(cp) + return cp_group + + def prio_groups_generator(self) -> Generator[Tuple[Optional[List[Chargepoint]], Optional[List[Chargepoint]]], + None, + None]: + groups = self._get_prio_groups() + start_index = 0 + + if not groups: + yield None, None + + if groups[0]["is_full_power_group"]: + # Beginnt mit Blitz + next_full_power_group = groups[0] + next_low_power_group = None + start_index = 1 + yield None, self._convert_to_cp_group(next_full_power_group) + + for index in range(start_index, len(groups)): + try: + if groups[index]["is_full_power_group"]: + next_full_power_group = groups[index] + next_low_power_group = None + else: + next_low_power_group = groups[index] + if index + 1 < len(groups) and groups[index + 1]["is_full_power_group"]: + next_full_power_group = groups[index + 1] + else: + next_full_power_group = None + yield self._convert_to_cp_group(next_low_power_group), self._convert_to_cp_group(next_full_power_group) + except Exception: + log.exception("Fehler in der allgemeinen Zähler-Klasse") + def get_max_id_in_hierarchy(current_entry: List, max_id: int) -> int: for item in current_entry: From edbdc199bd76083c7a34d7b409b1c98b12ace264 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 30 Jan 2026 11:38:51 +0100 Subject: [PATCH 11/18] fix --- packages/control/algorithm/common.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/control/algorithm/common.py b/packages/control/algorithm/common.py index e269b5e29b..d3a2d9093f 100644 --- a/packages/control/algorithm/common.py +++ b/packages/control/algorithm/common.py @@ -198,6 +198,7 @@ def reset_current_to_target_current(cp_prio_group: List[Chargepoint]) -> None: aus Stufe 1 ist.""" for cp in cp_prio_group: try: - cp.data.set.target_current = cp.data.set.current + if cp.data.set.current is not None: + cp.data.set.target_current = cp.data.set.current except Exception: log.exception(f"Fehler im Algorithmus-Modul für Ladepunkt{cp.num}") From f0b66f9f621452abbaf19c911c5144989b5a5e53 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 30 Jan 2026 14:28:23 +0100 Subject: [PATCH 12/18] fix --- packages/control/algorithm/filter_chargepoints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/control/algorithm/filter_chargepoints.py b/packages/control/algorithm/filter_chargepoints.py index 692b5ef100..2b3e6cd691 100644 --- a/packages/control/algorithm/filter_chargepoints.py +++ b/packages/control/algorithm/filter_chargepoints.py @@ -36,7 +36,7 @@ def get_chargepoints_by_mode(chargemodes: Tuple[Tuple[Optional[str], str]]) -> L def get_chargepoints_by_mode_and_lm_prio(chargemodes: Tuple[Tuple[Optional[str], str]], prio_group: List[Chargepoint]) -> List[Chargepoint]: cps_by_mode = get_chargepoints_by_mode(chargemodes) - return list(filter(lambda cp: cp.num in cps_by_mode, prio_group)) + return [cp for cp in prio_group if cp in cps_by_mode] def get_preferenced_chargepoint_charging( From 7de7741700d87be99c1c683a2f9bd88d60448320 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Mon, 2 Feb 2026 09:26:42 +0100 Subject: [PATCH 13/18] fixes ,prio list test --- .../control/algorithm/additional_current.py | 2 +- .../algorithm/filter_chargepoints_test.py | 6 +- .../algorithm/integration_test/conftest.py | 1 + .../integration_test/instant_charging_test.py | 6 + packages/control/algorithm/min_current.py | 2 +- .../control/algorithm/surplus_controlled.py | 2 +- .../algorithm/surplus_controlled_test.py | 4 +- packages/control/bat_all_test.py | 4 +- packages/control/counter_all.py | 5 + ..._test.py => counter_all_hierarchy_test.py} | 0 packages/control/counter_all_prio_test.py | 145 ++++++++++++++++++ 11 files changed, 167 insertions(+), 10 deletions(-) rename packages/control/{hierarchy_test.py => counter_all_hierarchy_test.py} (100%) create mode 100644 packages/control/counter_all_prio_test.py diff --git a/packages/control/algorithm/additional_current.py b/packages/control/algorithm/additional_current.py index e95baf3d35..4ae9519563 100644 --- a/packages/control/algorithm/additional_current.py +++ b/packages/control/algorithm/additional_current.py @@ -27,7 +27,7 @@ def set_additional_current(self, cp_prio_group: List[Chargepoint]) -> None: f"counter{counter.num}", cp_prio_group)) if preferenced_chargepoints: common.update_raw_data(preferenced_chargepoints) - log.info(f"Zähler {counter.num}, Verbraucher {preferenced_chargepoints}") + log.info(f"Zähler {counter.num}, Verbraucher {[f'LP{cp.num}' for cp in preferenced_chargepoints]}") while len(preferenced_chargepoints): cp = preferenced_chargepoints[0] missing_currents, counts = common.get_missing_currents_left(preferenced_chargepoints) diff --git a/packages/control/algorithm/filter_chargepoints_test.py b/packages/control/algorithm/filter_chargepoints_test.py index a34c5c641d..965307fed2 100644 --- a/packages/control/algorithm/filter_chargepoints_test.py +++ b/packages/control/algorithm/filter_chargepoints_test.py @@ -152,12 +152,12 @@ def test_get_chargepoints_by_mode_and_counter(chargepoints_of_counter: List[str] get_chargepoints_of_counter_mock = Mock(return_value=chargepoints_of_counter) monkeypatch.setattr(CounterAll, "get_chargepoints_of_counter", get_chargepoints_of_counter_mock) - get_loadmanagement_prios_mock = Mock(return_value=chargepoints_by_mode) - monkeypatch.setattr(filter_chargepoints, "get_loadmanagement_prios", get_loadmanagement_prios_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) data.data.counter_all_data = CounterAll() # evaluation - valid_chargepoints = filter_chargepoints.get_chargepoints_by_mode_and_counter(Mock(), "counter6") + valid_chargepoints = filter_chargepoints.get_chargepoints_by_mode_and_counter_and_lm_prio(Mock(), "counter6") # assertion assert valid_chargepoints == expected_chargepoints diff --git a/packages/control/algorithm/integration_test/conftest.py b/packages/control/algorithm/integration_test/conftest.py index a19c3b56ca..7e731ed347 100644 --- a/packages/control/algorithm/integration_test/conftest.py +++ b/packages/control/algorithm/integration_test/conftest.py @@ -51,6 +51,7 @@ def data_() -> None: data.data.counter_all_data = CounterAll() data.data.counter_all_data.data.get.hierarchy = NESTED_HIERARCHY data.data.counter_all_data.data.get.loadmanagement_prios = [{"type": "vehicle", "id": 0}] + data.data.ev_data["ev0"] = Ev(0) data.data.counter_all_data.data.config.consider_less_charging = True data.data.io_actions = IoActions() diff --git a/packages/control/algorithm/integration_test/instant_charging_test.py b/packages/control/algorithm/integration_test/instant_charging_test.py index a1ea40cd40..9a785e9f05 100644 --- a/packages/control/algorithm/integration_test/instant_charging_test.py +++ b/packages/control/algorithm/integration_test/instant_charging_test.py @@ -48,6 +48,9 @@ def all_cp_instant_charging_3p(): data.data.cp_data[f"cp{i}"].data.config.ev = i data.data.counter_all_data.data.get.loadmanagement_prios = [ {"type": "vehicle", "id": 3}, {"type": "vehicle", "id": 4}, {"type": "vehicle", "id": 5}] + data.data.ev_data["ev3"] = Ev(3) + data.data.ev_data["ev4"] = Ev(4) + data.data.ev_data["ev5"] = Ev(5) @dataclass @@ -188,10 +191,13 @@ def test_control_parameter_instant_charging(params: ParamsControlParameter, all_ # setup data.data.cp_data["cp3"].data.control_parameter.submode = params.submode_cp3 data.data.cp_data["cp3"].data.set.charging_ev_data.data.full_power = params.full_power_cp3 + data.data.ev_data["ev3"].data.full_power = params.full_power_cp3 data.data.cp_data["cp4"].data.control_parameter.submode = params.submode_cp4 data.data.cp_data["cp4"].data.set.charging_ev_data.data.full_power = params.full_power_cp4 + data.data.ev_data["ev4"].data.full_power = params.full_power_cp4 data.data.cp_data["cp5"].data.control_parameter.submode = params.submode_cp5 data.data.cp_data["cp5"].data.set.charging_ev_data.data.full_power = params.full_power_cp5 + data.data.ev_data["ev5"].data.full_power = params.full_power_cp5 data.data.counter_data["counter0"].data.set.raw_power_left = 22080 data.data.counter_data["counter0"].data.set.raw_currents_left = [32]*3 data.data.counter_data["counter6"].data.set.raw_currents_left = [16]*3 diff --git a/packages/control/algorithm/min_current.py b/packages/control/algorithm/min_current.py index de6851990d..fb00789cf7 100644 --- a/packages/control/algorithm/min_current.py +++ b/packages/control/algorithm/min_current.py @@ -24,7 +24,7 @@ def set_min_current(self, cp_prio_group) -> None: f"counter{counter.num}", cp_prio_group) if preferenced_chargepoints: - log.info(f"Zähler {counter.num}, Verbraucher {preferenced_chargepoints}") + log.info(f"Zähler {counter.num}, Verbraucher {[f'LP{cp.num}' for cp in preferenced_chargepoints]}") common.update_raw_data(preferenced_chargepoints, diff_to_zero=True) while len(preferenced_chargepoints): cp = preferenced_chargepoints[0] diff --git a/packages/control/algorithm/surplus_controlled.py b/packages/control/algorithm/surplus_controlled.py index cf64f17c34..5f9d38b767 100644 --- a/packages/control/algorithm/surplus_controlled.py +++ b/packages/control/algorithm/surplus_controlled.py @@ -58,7 +58,7 @@ def _set(self, chargepoints: List[Chargepoint], feed_in_yield: Optional[int], counter: Counter) -> None: - log.info(f"Zähler {counter.num}, Verbraucher {chargepoints}") + log.info(f"Zähler {counter.num}, Verbraucher {[f'LP{cp.num}' for cp in chargepoints]}") common.update_raw_data(chargepoints, surplus=True) while len(chargepoints): cp = chargepoints[0] diff --git a/packages/control/algorithm/surplus_controlled_test.py b/packages/control/algorithm/surplus_controlled_test.py index 7a3521b746..560ac6b478 100644 --- a/packages/control/algorithm/surplus_controlled_test.py +++ b/packages/control/algorithm/surplus_controlled_test.py @@ -96,11 +96,11 @@ 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_loadmanagement_prios", + monkeypatch.setattr(surplus_controlled, "get_chargepoints_by_mode_and_lm_prio", mock_get_chargepoints_surplus_controlled) # execution - SurplusControlled().set_required_current_to_max() + SurplusControlled().set_required_current_to_max([mock_cp1]) # evaluation assert mock_cp1.data.control_parameter.required_currents == expected_currents diff --git a/packages/control/bat_all_test.py b/packages/control/bat_all_test.py index 39f83a5ccb..a0c121bb46 100644 --- a/packages/control/bat_all_test.py +++ b/packages/control/bat_all_test.py @@ -209,8 +209,8 @@ def test_get_power_limit(params: PowerLimitParams, data_, monkeypatch): data.data.counter_data["counter0"].data.get.power = params.evu_power data.data.bat_all_data = b_all - get_loadmanagement_prios_mock = Mock(return_value=params.cps) - monkeypatch.setattr(bat_all, "get_loadmanagement_prios", get_loadmanagement_prios_mock) + get_chargepoints_by_mode_mock = Mock(return_value=params.cps) + monkeypatch.setattr(bat_all, "get_chargepoints_by_mode", get_chargepoints_by_mode_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/counter_all.py b/packages/control/counter_all.py index d2eeec3dff..2fb0d8586d 100644 --- a/packages/control/counter_all.py +++ b/packages/control/counter_all.py @@ -531,6 +531,7 @@ def prio_groups_generator(self) -> Generator[Tuple[Optional[List[Chargepoint]], None]: groups = self._get_prio_groups() start_index = 0 + skip_next = False if not groups: yield None, None @@ -544,6 +545,9 @@ def prio_groups_generator(self) -> Generator[Tuple[Optional[List[Chargepoint]], for index in range(start_index, len(groups)): try: + if skip_next: + skip_next = False + continue if groups[index]["is_full_power_group"]: next_full_power_group = groups[index] next_low_power_group = None @@ -551,6 +555,7 @@ def prio_groups_generator(self) -> Generator[Tuple[Optional[List[Chargepoint]], next_low_power_group = groups[index] if index + 1 < len(groups) and groups[index + 1]["is_full_power_group"]: next_full_power_group = groups[index + 1] + skip_next = True else: next_full_power_group = None yield self._convert_to_cp_group(next_low_power_group), self._convert_to_cp_group(next_full_power_group) diff --git a/packages/control/hierarchy_test.py b/packages/control/counter_all_hierarchy_test.py similarity index 100% rename from packages/control/hierarchy_test.py rename to packages/control/counter_all_hierarchy_test.py diff --git a/packages/control/counter_all_prio_test.py b/packages/control/counter_all_prio_test.py new file mode 100644 index 0000000000..565e879c53 --- /dev/null +++ b/packages/control/counter_all_prio_test.py @@ -0,0 +1,145 @@ +from dataclasses import dataclass +from unittest.mock import Mock + +import pytest +from control import data +from control.chargepoint.chargepoint import Chargepoint +from control.counter_all import CounterAll +from control.ev.ev import Ev + + +@pytest.fixture() +def mock_data() -> None: + data.data_init(Mock()) + + +@dataclass +class EvFullPowerState: + ev0_full_power: bool = False + ev1_full_power: bool = False + ev2_full_power: bool = False + ev3_full_power: bool = False + ev4_full_power: bool = False + + +PRIO_LIST_FLASH_END = [{"type": "vehicle", "id": 0}, + {"type": "vehicle", "id": 1}, + {"type": "vehicle", "id": 2}, + {"type": "vehicle", "id": 4}] +PRIO_LIST_FLASH_START = [{"type": "vehicle", "id": 0}, + {"type": "vehicle", "id": 3}, + {"type": "vehicle", "id": 2}] +PRIO_LIST_DOUBLE_FLASH_START = [{"type": "vehicle", "id": 0}, + {"type": "vehicle", "id": 1}, + {"type": "vehicle", "id": 3}, + {"type": "vehicle", "id": 2}] +PRIO_LIST_DOUBLE_FLASH_MIDDLE = [{"type": "vehicle", "id": 4}, + {"type": "vehicle", "id": 0}, + {"type": "vehicle", "id": 1}, + {"type": "vehicle", "id": 3}, + {"type": "vehicle", "id": 2}] +PRIO_LIST_DOUBLE_FLASH_END = [{"type": "vehicle", "id": 4}, + {"type": "vehicle", "id": 0}, + {"type": "vehicle", "id": 1}] +PRIO_LIST_NO_FLASH_END = [{"type": "vehicle", "id": 4}, + {"type": "vehicle", "id": 1}] +PRIO_LIST_NO_FLASH_MIDDLE = [{"type": "vehicle", "id": 2}, + {"type": "vehicle", "id": 4}, + {"type": "vehicle", "id": 1}] +GROUPED_PAYLOAD_FLASH_MIDDLE = [ + {'items': [{'type': 'vehicle', 'id': 0}, {'type': 'vehicle', 'id': 1}], 'is_full_power_group': False}, + {'items': [{'type': 'vehicle', 'id': 2}], 'is_full_power_group': True}, + {'items': [{'type': 'vehicle', 'id': 4}], 'is_full_power_group': False}] +GROUPED_PAYLOAD_FLASH_START = [ + {'items': [{'type': 'vehicle', 'id': 0}], 'is_full_power_group': True}, + {'items': [{'type': 'vehicle', 'id': 3}, {'type': 'vehicle', 'id': 2}], 'is_full_power_group': False}] +GROUPED_PAYLOAD_DOUBLE_FLASH_START = [ + {'items': [{'type': 'vehicle', 'id': 0}], 'is_full_power_group': True}, + {'items': [{'type': 'vehicle', 'id': 1}], 'is_full_power_group': True}, + {'items': [{'type': 'vehicle', 'id': 3}, {'type': 'vehicle', 'id': 2}], 'is_full_power_group': False}] +GROUPED_PAYLOAD_DOUBLE_FLASH_MIDDLE = [ + {'items': [{'type': 'vehicle', 'id': 4}], 'is_full_power_group': False}, + {'items': [{'type': 'vehicle', 'id': 0}], 'is_full_power_group': True}, + {'items': [{'type': 'vehicle', 'id': 1}], 'is_full_power_group': True}, + {'items': [{'type': 'vehicle', 'id': 3}, {'type': 'vehicle', 'id': 2}], 'is_full_power_group': False}] +GROUPED_PAYLOAD_DOUBLE_FLASH_END = [ + {'items': [{'type': 'vehicle', 'id': 4}], 'is_full_power_group': False}, + {'items': [{'type': 'vehicle', 'id': 0}], 'is_full_power_group': True}, + {'items': [{'type': 'vehicle', 'id': 1}], 'is_full_power_group': True}] +GROUPED_PAYLOAD_FLASH_END = [ + {'items': [{'type': 'vehicle', 'id': 4}], 'is_full_power_group': False}, + {'items': [{'type': 'vehicle', 'id': 1}], 'is_full_power_group': True}] +GROUPED_PAYLOAD_NO_FLASH_MIDDLE = [ + {'items': [{'type': 'vehicle', 'id': 2}], 'is_full_power_group': True}, + {'items': [{'type': 'vehicle', 'id': 4}], 'is_full_power_group': False}, + {'items': [{'type': 'vehicle', 'id': 1}], 'is_full_power_group': True}] + + +@pytest.mark.parametrize("loadmanagement_prios, ev_full_power_state, expected_grouped_payload", [ + pytest.param(PRIO_LIST_FLASH_END, EvFullPowerState(ev2_full_power=True), GROUPED_PAYLOAD_FLASH_MIDDLE,), + pytest.param(PRIO_LIST_FLASH_START, EvFullPowerState(ev0_full_power=True), GROUPED_PAYLOAD_FLASH_START,), + pytest.param(PRIO_LIST_DOUBLE_FLASH_START, EvFullPowerState(ev0_full_power=True, ev1_full_power=True), + GROUPED_PAYLOAD_DOUBLE_FLASH_START,), + pytest.param(PRIO_LIST_DOUBLE_FLASH_MIDDLE, EvFullPowerState(ev0_full_power=True, ev1_full_power=True), + GROUPED_PAYLOAD_DOUBLE_FLASH_MIDDLE,), + pytest.param(PRIO_LIST_DOUBLE_FLASH_END, EvFullPowerState(ev0_full_power=True, ev1_full_power=True), + GROUPED_PAYLOAD_DOUBLE_FLASH_END,), + pytest.param(PRIO_LIST_NO_FLASH_END, EvFullPowerState(ev1_full_power=True), GROUPED_PAYLOAD_FLASH_END,), + pytest.param(PRIO_LIST_NO_FLASH_MIDDLE, EvFullPowerState(ev2_full_power=True, ev1_full_power=True), + GROUPED_PAYLOAD_NO_FLASH_MIDDLE,), +] +) +def test_get_prio_groups(loadmanagement_prios, + ev_full_power_state: EvFullPowerState, + expected_grouped_payload, + mock_data): + # setup + c = CounterAll() + c.data.get.loadmanagement_prios = loadmanagement_prios + for i in range(5): + data.data.ev_data[f"ev{i}"] = Ev(i) + data.data.ev_data[f"ev{i}"].data.full_power = getattr(ev_full_power_state, f"ev{i}_full_power") + + # execution + prio_groups = c._get_prio_groups() + + # verification + assert prio_groups == expected_grouped_payload + + +@pytest.mark.parametrize("prio_groups, expected_groups", [ + pytest.param(GROUPED_PAYLOAD_FLASH_MIDDLE, [([0, 1], [2]), ([4], None)]), + pytest.param(GROUPED_PAYLOAD_FLASH_START, [(None, [0]), ([3, 2], None)]), + pytest.param(GROUPED_PAYLOAD_DOUBLE_FLASH_START, [(None, [0]), (None, [1]), ([3, 2], None)]), + pytest.param(GROUPED_PAYLOAD_DOUBLE_FLASH_MIDDLE, [([4], [0]), (None, [1]), ([3, 2], None)]), + pytest.param(GROUPED_PAYLOAD_DOUBLE_FLASH_END, [([4], [0]), (None, [1])]), + pytest.param(GROUPED_PAYLOAD_FLASH_END, [([4], [1])]), + pytest.param(GROUPED_PAYLOAD_NO_FLASH_MIDDLE, [(None, [2]), ([4], [1])]), +] +) +def test_prio_groups_generator(prio_groups, expected_groups, mock_data, monkeypatch): + # setup + c = CounterAll() + for i in range(5): + data.data.ev_data[f"ev{i}"] = Ev(i) + for i in range(5): + data.data.cp_data[f"cp{i}"] = Chargepoint(i, None) + data.data.cp_data[f"cp{i}"].data.config.ev = i + + mock_get_prio_groups = Mock(return_value=prio_groups) + monkeypatch.setattr(CounterAll, "_get_prio_groups", mock_get_prio_groups) + + # execution + gen = c.prio_groups_generator() + + # verification + for t in expected_groups: + generated = next(gen) + if t[0] is None: + assert generated[0] is None + else: + assert [cp.num for cp in generated[0]] == t[0] + if t[1] is None: + assert generated[1] is None + else: + assert [cp.num for cp in generated[1]] == t[1] From 0b81fe931c34fb2776476833d047fc25fa7fa5ee Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Mon, 2 Feb 2026 10:17:59 +0100 Subject: [PATCH 14/18] divide current betwenn all 3 cps --- .../control/algorithm/integration_test/instant_charging_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/control/algorithm/integration_test/instant_charging_test.py b/packages/control/algorithm/integration_test/instant_charging_test.py index 9a785e9f05..d94e900454 100644 --- a/packages/control/algorithm/integration_test/instant_charging_test.py +++ b/packages/control/algorithm/integration_test/instant_charging_test.py @@ -177,7 +177,7 @@ class ParamsControlParameter(ParamsExpectedSetCurrent, ParamsExpectedCounterSet) expected_raw_currents_left_counter6=[0]*3), ParamsControlParameter(name="change submode cp4", submode_cp4=Chargemode.TIME_CHARGING, - expected_current_cp3=14, + expected_current_cp3=13.333333333333334, expected_current_cp4=8, expected_current_cp5=8, expected_raw_power_left=1380, From 0a0cfde17a106496f4b949665bc9a8aebd34c140 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Mon, 2 Feb 2026 10:40:34 +0100 Subject: [PATCH 15/18] pytest --- .../algorithm/filter_chargepoints_test.py | 23 +++++++++---------- .../integration_test/instant_charging_test.py | 4 ++-- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/packages/control/algorithm/filter_chargepoints_test.py b/packages/control/algorithm/filter_chargepoints_test.py index 965307fed2..b3c03f4c42 100644 --- a/packages/control/algorithm/filter_chargepoints_test.py +++ b/packages/control/algorithm/filter_chargepoints_test.py @@ -14,6 +14,11 @@ from control.ev.ev import Ev, EvData, Get +@pytest.fixture() +def mock_data() -> None: + data.data_init(Mock()) + + @dataclass class PreferencedParams: name: str @@ -96,17 +101,12 @@ def mock_cp(cp: Chargepoint, num: int): @pytest.mark.parametrize( - "required_current_1, loadmanagement_prios, expected_cp_indices", + "required_current_1, expected_cp_indices", [ - pytest.param(6, [{"type": "vehicle", "id": 1}, {"type": "vehicle", "id": 2}], - [1, 2], id="fits mode"), - pytest.param(0, [{"type": "vehicle", "id": 1}, {"type": "vehicle", "id": 2}], - [2], id="cp1 should not charge"), - pytest.param(6, [{"type": "vehicle", "id": 2}, {"type": "vehicle", "id": 1}], - [2, 1], id="cp2 is prioritized") + pytest.param(6, [1, 2], id="fits mode"), + pytest.param(0, [2], id="cp1 should not charge"), ]) def test_get_chargepoints_by_mode(required_current_1: int, - loadmanagement_prios: List[Dict], expected_cp_indices, mock_cp1, mock_cp2): # setup @@ -120,8 +120,6 @@ def setup_cp(cp: Chargepoint, required_current: float) -> Chargepoint: return cp data.data.cp_data = {"cp1": setup_cp(mock_cp1, required_current_1), "cp2": setup_cp(mock_cp2, 6)} - data.data.counter_all_data = CounterAll() - data.data.counter_all_data.data.get.loadmanagement_prios = loadmanagement_prios # evaluation valid_chargepoints = filter_chargepoints.get_chargepoints_by_mode( @@ -144,7 +142,7 @@ def setup_cp(cp: Chargepoint, required_current: float) -> Chargepoint: def test_get_chargepoints_by_mode_and_counter(chargepoints_of_counter: List[str], chargepoints_by_mode_indices: List[int], expected_cp_indices: List[int], - monkeypatch, mock_cp1, mock_cp2): + monkeypatch, mock_cp1, mock_cp2, mock_data): # setup cp_mapping = {1: mock_cp1, 2: mock_cp2} chargepoints_by_mode = [cp_mapping[i] for i in chargepoints_by_mode_indices] @@ -157,7 +155,8 @@ def test_get_chargepoints_by_mode_and_counter(chargepoints_of_counter: List[str] data.data.counter_all_data = CounterAll() # evaluation - valid_chargepoints = filter_chargepoints.get_chargepoints_by_mode_and_counter_and_lm_prio(Mock(), "counter6") + valid_chargepoints = filter_chargepoints.get_chargepoints_by_mode_and_counter_and_lm_prio(Mock(), "counter6", ( + mock_cp1, mock_cp2)) # assertion assert valid_chargepoints == expected_chargepoints diff --git a/packages/control/algorithm/integration_test/instant_charging_test.py b/packages/control/algorithm/integration_test/instant_charging_test.py index d94e900454..c0a857ba23 100644 --- a/packages/control/algorithm/integration_test/instant_charging_test.py +++ b/packages/control/algorithm/integration_test/instant_charging_test.py @@ -180,8 +180,8 @@ class ParamsControlParameter(ParamsExpectedSetCurrent, ParamsExpectedCounterSet) expected_current_cp3=13.333333333333334, expected_current_cp4=8, expected_current_cp5=8, - expected_raw_power_left=1380, - expected_raw_currents_left_counter0=[2]*3, + expected_raw_power_left=1840, + expected_raw_currents_left_counter0=[2.666666666666666]*3, expected_raw_currents_left_counter6=[0]*3), ] From aad079eabd83339b8b01458d85e9922e209b7a0d Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Mon, 2 Feb 2026 11:04:54 +0100 Subject: [PATCH 16/18] flake8 --- packages/control/algorithm/filter_chargepoints_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/control/algorithm/filter_chargepoints_test.py b/packages/control/algorithm/filter_chargepoints_test.py index b3c03f4c42..7996a1b3a1 100644 --- a/packages/control/algorithm/filter_chargepoints_test.py +++ b/packages/control/algorithm/filter_chargepoints_test.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Dict, List +from typing import List from unittest.mock import Mock import pytest From be83024247bfcad99c0930ed4fdac6f7aa9b30b3 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Tue, 3 Feb 2026 11:24:49 +0100 Subject: [PATCH 17/18] fix --- packages/control/algorithm/bidi_charging.py | 1 + packages/control/algorithm/common.py | 10 ++++------ packages/control/ev/ev.py | 3 ++- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/control/algorithm/bidi_charging.py b/packages/control/algorithm/bidi_charging.py index bba6fa8536..22313c4f57 100644 --- a/packages/control/algorithm/bidi_charging.py +++ b/packages/control/algorithm/bidi_charging.py @@ -42,6 +42,7 @@ def set_bidi(self): missing_currents[index] = cp.check_min_max_current(missing_currents[index], cp.data.get.phases_in_use) grid_counter.update_surplus_values_left(missing_currents, voltages_mean(cp.data.get.voltages)) + grid_counter.update_values_left(missing_currents, voltages_mean(cp.data.get.voltages)) cp.data.set.current = missing_currents[0] log.info(f"LP{cp.num}: Stromstärke {missing_currents}A") preferenced_cps.pop(0) diff --git a/packages/control/algorithm/common.py b/packages/control/algorithm/common.py index d3a2d9093f..4cab3e212b 100644 --- a/packages/control/algorithm/common.py +++ b/packages/control/algorithm/common.py @@ -77,10 +77,9 @@ def set_current_counterdiff(diff_current: float, data.data.counter_data[counter].update_surplus_values_left( diffs, voltages_mean(chargepoint.data.get.voltages)) - else: - data.data.counter_data[counter].update_values_left( - diffs, - voltages_mean(chargepoint.data.get.voltages)) + data.data.counter_data[counter].update_values_left( + diffs, + voltages_mean(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 @@ -155,8 +154,7 @@ def update_raw_data(preferenced_chargepoints: List[Chargepoint], data.data.counter_data[counter].update_surplus_values_left( diffs, voltages_mean(chargepoint.data.get.voltages)) - else: - data.data.counter_data[counter].update_values_left(diffs, voltages_mean(chargepoint.data.get.voltages)) + data.data.counter_data[counter].update_values_left(diffs, voltages_mean(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/ev/ev.py b/packages/control/ev/ev.py index 075b4adb52..25ecc812e7 100644 --- a/packages/control/ev/ev.py +++ b/packages/control/ev/ev.py @@ -39,7 +39,8 @@ def get_vehicle_default() -> dict: "model": None, }, "tag_id": [], - "get/soc": 0 + "get/soc": 0, + "full_power": False, } From 9aa488df08442663e54fd7a8e0677ecc61ae3f47 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Thu, 5 Feb 2026 08:38:30 +0100 Subject: [PATCH 18/18] fix pytest --- packages/control/algorithm/bidi_charging.py | 1 + packages/control/algorithm/common.py | 4 +++- .../integration_test/pv_charging_test.py | 6 +++--- packages/control/counter.py | 16 ++++++++-------- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/control/algorithm/bidi_charging.py b/packages/control/algorithm/bidi_charging.py index 22313c4f57..35f0311910 100644 --- a/packages/control/algorithm/bidi_charging.py +++ b/packages/control/algorithm/bidi_charging.py @@ -43,6 +43,7 @@ def set_bidi(self): cp.data.get.phases_in_use) grid_counter.update_surplus_values_left(missing_currents, voltages_mean(cp.data.get.voltages)) grid_counter.update_values_left(missing_currents, voltages_mean(cp.data.get.voltages)) + grid_counter.update_currents_left(missing_currents) cp.data.set.current = missing_currents[0] log.info(f"LP{cp.num}: Stromstärke {missing_currents}A") preferenced_cps.pop(0) diff --git a/packages/control/algorithm/common.py b/packages/control/algorithm/common.py index 4cab3e212b..533f90a273 100644 --- a/packages/control/algorithm/common.py +++ b/packages/control/algorithm/common.py @@ -80,6 +80,7 @@ def set_current_counterdiff(diff_current: float, data.data.counter_data[counter].update_values_left( diffs, voltages_mean(chargepoint.data.get.voltages)) + data.data.counter_data[counter].update_currents_left(diffs) data.data.io_actions.dimming_set_import_power_left({"type": "cp", "id": chargepoint.num}, sum(diffs)*230) chargepoint.data.set.current = current @@ -155,10 +156,11 @@ def update_raw_data(preferenced_chargepoints: List[Chargepoint], diffs, voltages_mean(chargepoint.data.get.voltages)) data.data.counter_data[counter].update_values_left(diffs, voltages_mean(chargepoint.data.get.voltages)) + data.data.counter_data[counter].update_currents_left(diffs) 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: +def consider_less_charging_chargepoint_in_loadmanagement(cp: Chargepoint, set_current: float) -> float: 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) > get_medium_charging_current( diff --git a/packages/control/algorithm/integration_test/pv_charging_test.py b/packages/control/algorithm/integration_test/pv_charging_test.py index 105e421470..d5688316f5 100644 --- a/packages/control/algorithm/integration_test/pv_charging_test.py +++ b/packages/control/algorithm/integration_test/pv_charging_test.py @@ -183,7 +183,7 @@ def test_pv_delay_expired(all_cp_pv_charging_3p, all_cp_not_charging, monkeypatc expected_current_cp3=16, expected_current_cp4=8, expected_current_cp5=8, - expected_raw_power_left=34820, + expected_raw_power_left=27920, expected_surplus_power_left=1090, expected_reserved_surplus=0, expected_released_surplus=0), @@ -195,7 +195,7 @@ def test_pv_delay_expired(all_cp_pv_charging_3p, all_cp_not_charging, monkeypatc expected_current_cp3=16, expected_current_cp4=7.8731884057971016, expected_current_cp5=7.8731884057971016, - expected_raw_power_left=24470, + expected_raw_power_left=17745, expected_surplus_power_left=1090, expected_reserved_surplus=0, expected_released_surplus=0), @@ -260,7 +260,7 @@ def test_surplus(params: ParamsSurplus, all_cp_pv_charging_3p, all_cp_charging_3 expected_current_cp3=32, expected_current_cp4=6, expected_current_cp5=6, - expected_raw_power_left=37520.0, + expected_raw_power_left=32460.0, expected_surplus_power_left=3000, expected_reserved_surplus=0, expected_released_surplus=0) diff --git a/packages/control/counter.py b/packages/control/counter.py index ff166e0040..94a9c4625d 100644 --- a/packages/control/counter.py +++ b/packages/control/counter.py @@ -194,21 +194,21 @@ def _set_power_left(self, loadmanagement_available: bool) -> None: else: self.data.set.raw_power_left = None - def update_values_left(self, diffs, cp_voltage: float) -> None: + def update_values_left(self, diffs: List[float], cp_voltage: float) -> None: # Mittelwert der Spannungen verwenden, um Phasenverdrehung zu kompensieren # (Probleme bei einphasig angeschlossenen Wallboxen) - 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([c * cp_voltage for c in diffs]) - 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') + log.debug(f'Zähler {self.num}: {self.data.set.raw_power_left}W verbleibende Leistung') - def update_surplus_values_left(self, diffs, cp_voltage: float) -> None: - self.data.set.raw_currents_left = list(map(operator.sub, self.data.set.raw_currents_left, diffs)) + def update_surplus_values_left(self, diffs: List[float], cp_voltage: float) -> None: if self.data.set.surplus_power_left: self.data.set.surplus_power_left -= sum([c * cp_voltage for c in diffs]) - 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') + log.debug(f'Zähler {self.num}: {self.data.set.surplus_power_left}W verbleibender Überschuss') + + def update_currents_left(self, diffs: List[float]) -> None: + self.data.set.raw_currents_left = list(map(operator.sub, self.data.set.raw_currents_left, diffs)) + log.debug(f'Zähler {self.num}: {self.data.set.raw_currents_left}A verbleibende Ströme') def calc_surplus(self): # reservierte Leistung wird nicht berücksichtigt, weil diese noch verwendet werden kann, bis die EV