diff --git a/packages/control/algorithm/common.py b/packages/control/algorithm/common.py index 3a91068201..9027c97259 100644 --- a/packages/control/algorithm/common.py +++ b/packages/control/algorithm/common.py @@ -6,6 +6,7 @@ from control.algorithm.utils import get_medium_charging_current from control.chargepoint.chargepoint import Chargepoint from control.counter import Counter +from control.load import get_load_str from control.load_protocol import Load from helpermodules.phase_handling import voltages_mean from helpermodules.timecheck import check_timestamp @@ -25,8 +26,7 @@ def reset_current(): load.data.set.current = None load.data.set.target_current = 0 except Exception: - log.exception(f"Fehler im Algorithmus-Modul für " - f"{'Ladepunkt' if isinstance(load, Chargepoint) else 'Verbraucher'}{load.num}") + log.exception(f"Fehler im Algorithmus-Modul für {get_load_str(load)}") def reset_current_by_chargemode(chargemodes: Tuple[Tuple[Optional[str], str]]) -> None: @@ -66,11 +66,8 @@ def set_current_counterdiff(diff_current: float, load: Load, surplus: bool = False) -> None: required_currents = load.data.control_parameter.required_currents - if isinstance(load, Chargepoint): - considered_current = consider_less_charging_chargepoint_in_loadmanagement( - load, current) - else: - considered_current = current + considered_current = consider_less_charging_chargepoint_in_loadmanagement( + load, current) # gar nicht ladende Autos? diff = max(considered_current - diff_current, 0) diffs = [diff if required_currents[i] != 0 else 0 for i in range(3)] @@ -90,7 +87,7 @@ def set_current_counterdiff(diff_current: float, data.data.io_actions.set_limit_loadmanager(sum(diffs)*230) load.data.set.current = current - log.info(f"{'LP' if isinstance(load, Chargepoint) else 'Verbraucher'} {load.num}: Stromstärke {current}A") + log.info(f"{get_load_str(load)}: Stromstärke {current}A") # tested @@ -136,9 +133,8 @@ def update_raw_data(preferenced_loads: List[Load], for load in preferenced_loads: required_currents = load.data.control_parameter.required_currents max_target_set_current = max(load.data.set.target_current, load.data.set.current or 0) - if isinstance(load, Chargepoint): - max_target_set_current = consider_less_charging_chargepoint_in_loadmanagement( - load, max_target_set_current) + max_target_set_current = consider_less_charging_chargepoint_in_loadmanagement( + load, max_target_set_current) if diff_to_zero is False: if load.data.control_parameter.min_current < max_target_set_current: @@ -169,18 +165,24 @@ def update_raw_data(preferenced_loads: List[Load], data.data.io_actions.set_limit_loadmanager(sum(diffs)*230) -def consider_less_charging_chargepoint_in_loadmanagement(cp: Chargepoint, set_current: float) -> bool: - if (data.data.counter_all_data.data.config.consider_less_charging is False and - ((set_current - - cp.data.set.charging_ev_data.ev_template.data.nominal_difference) > get_medium_charging_current( - cp.data.get.currents) and - cp.data.control_parameter.timestamp_charge_start is not None and - check_timestamp(cp.data.control_parameter.timestamp_charge_start, LESS_CHARGING_TIMEOUT) is False)): - log.debug( - f"LP {cp.num} lädt deutlich unter dem Sollstrom und wird nur mit {cp.data.get.currents}A berücksichtigt.") - return get_medium_charging_current(cp.data.get.currents) +def consider_less_charging_chargepoint_in_loadmanagement(load: Load, set_current: float) -> float: + if isinstance(load, Chargepoint): + expected_current = set_current - load.data.set.charging_ev_data.ev_template.data.nominal_difference else: - return set_current + expected_current = set_current - 1 + expected_current = max(expected_current, 0) + if data.data.counter_all_data.data.config.consider_less_charging is False: + if expected_current >= get_medium_charging_current(load.data.get.currents): + if load.data.control_parameter.timestamp_charge_start is not None: + if check_timestamp(load.data.control_parameter.timestamp_charge_start, LESS_CHARGING_TIMEOUT) is False: + log.debug(f"{get_load_str(load)} lädt deutlich unter dem Sollstrom und " + f"wird nur mit {load.data.get.currents}A berücksichtigt.") + return get_medium_charging_current(load.data.get.currents) + elif load.data.set.current_prev != 0: + log.debug( + f"{get_load_str(load)} lädt nicht und wird nur mit {load.data.get.currents}A berücksichtigt.") + return get_medium_charging_current(load.data.get.currents) + return set_current # tested @@ -209,5 +211,4 @@ def reset_current_to_target_current(): try: load.data.set.target_current = load.data.set.current except Exception: - log.exception(f"Fehler im Algorithmus-Modul für " - f"{'Ladepunkt' if isinstance(load, Chargepoint) else 'Verbraucher'}{load.num}") + log.exception(f"Fehler im Algorithmus-Modul für {get_load_str(load)}") diff --git a/packages/control/algorithm/common_test.py b/packages/control/algorithm/common_test.py index 347cd87e1f..ff9a584c20 100644 --- a/packages/control/algorithm/common_test.py +++ b/packages/control/algorithm/common_test.py @@ -9,6 +9,7 @@ from control.ev.ev import Ev from control.counter import Counter from control.counter_all.counter_all import CounterAll +from control.consumer.consumer import Consumer from control.io_device import IoActions @@ -176,3 +177,32 @@ def test_consider_less_charging_chargepoint_in_loadmanagement(consider_less_char # assertion assert considered == expected_considered + + +@pytest.mark.parametrize( + "consider_less_charging, set_current, get_currents, current_prev, expected_considered", + [ + pytest.param(False, 10, [2]*3, 1, 2, + id="consumer less charging considered via timestamp none and current_prev"), + pytest.param(False, 10, [10]*3, 1, 10, + id="consumer expected_current threshold not reached"), + pytest.param(True, 10, [0]*3, 10, 10, + id="consider_less_charging"), + ]) +def test_consider_less_charging_consumer_in_loadmanagement(consider_less_charging: bool, + set_current: float, + get_currents: List[float], + current_prev: float, + expected_considered: float): + # setup + consumer = Consumer(7) + consumer.data.get.currents = get_currents + consumer.data.set.current_prev = current_prev + consumer.data.control_parameter.timestamp_charge_start = 1652683152 + data.data.counter_all_data.data.config.consider_less_charging = consider_less_charging + + # evaluation + considered = common.consider_less_charging_chargepoint_in_loadmanagement(consumer, set_current) + + # assertion + assert considered == expected_considered diff --git a/packages/control/algorithm/filter_chargepoints.py b/packages/control/algorithm/filter_chargepoints.py index 603dee204f..7454fe9a83 100644 --- a/packages/control/algorithm/filter_chargepoints.py +++ b/packages/control/algorithm/filter_chargepoints.py @@ -5,9 +5,9 @@ from control import data from control.chargemode import Chargemode -from control.chargepoint.chargepoint import Chargepoint from control.consumer.consumer import Consumer from control.consumer.usage import ConsumerUsage +from control.load import get_load_str from control.load_protocol import Load log = logging.getLogger(__name__) @@ -84,12 +84,11 @@ def get_preferenced_load_charging( valid_group: List[Load] = [] for load in group: if load.data.set.target_current == 0: - log.info(f"{'LP' if isinstance(load, Chargepoint) else 'Verbraucher'} {load.num}: " + log.info(f"{get_load_str(load)}: " f"Keine Zuteilung des Mindeststroms, daher keine weitere Berücksichtigung") preferenced_loads_without_set_current.append(load) elif load.data.get.charge_state is False: - log.info(f"{'LP' if isinstance(load, Chargepoint) else 'Verbraucher'} {load.num}: " - f"Lädt nicht, daher keine weitere Berücksichtigung") + log.info(f"{get_load_str(load)}: Lädt nicht, daher keine weitere Berücksichtigung") preferenced_loads_without_set_current.append(load) elif (isinstance(load, Consumer) and load.data.usage.type in [ConsumerUsage.CONTINUOUS, ConsumerUsage.SUSPENDABLE_ONOFF]): @@ -103,4 +102,4 @@ def get_preferenced_load_charging( def filtered_loads_to_str(loads: List[Load]) -> str: - return ", ".join([f"{'LP' if isinstance(load, Chargepoint) else 'Verbraucher'}{load.num}" for load in loads]) + return ", ".join([get_load_str(load) for load in loads]) diff --git a/packages/control/chargepoint/chargepoint.py b/packages/control/chargepoint/chargepoint.py index 1880dfa8e5..a756fd4759 100644 --- a/packages/control/chargepoint/chargepoint.py +++ b/packages/control/chargepoint/chargepoint.py @@ -591,15 +591,6 @@ def set_required_currents(self, required_current: float) -> None: self.data.set.required_power = sum( [c * v for c, v in zip(control_parameter.required_currents, self.data.get.voltages)]) - def set_timestamp_charge_start(self): - # Beim Ladestart Timer laufen lassen, manche Fahrzeuge brauchen sehr lange. - # Nach dem Algorithmus setzen, sonst steht set current noch nicht fest. - if self.data.control_parameter.timestamp_charge_start is None: - if self.data.set.current_prev == 0 and self.data.set.current != 0: - self.data.control_parameter.timestamp_charge_start = create_timestamp() - elif self.data.set.current == 0: - self.data.control_parameter.timestamp_charge_start = None - def set_chargemode_changed(self, submode: Chargemode) -> None: if ((submode == Chargemode.TIME_CHARGING and self.data.control_parameter.chargemode != Chargemode.TIME_CHARGING) or diff --git a/packages/control/chargepoint/chargepoint_all.py b/packages/control/chargepoint/chargepoint_all.py index a384681e17..b3206bc419 100644 --- a/packages/control/chargepoint/chargepoint_all.py +++ b/packages/control/chargepoint/chargepoint_all.py @@ -2,7 +2,6 @@ import logging from control import data -from control.chargepoint.chargepoint_state import ChargepointState from helpermodules.constants import NO_ERROR @@ -37,35 +36,6 @@ def all_chargepoint_data_factory() -> AllChargepointData: class AllChargepoints: data: AllChargepointData = field(default_factory=all_chargepoint_data_factory) - def no_charge(self): - """ Wenn keine EV angesteckt sind oder keine Verzögerungen aktiv sind, werden die Algorithmus-Werte - zurückgesetzt. - (dient der Robustheit) - """ - try: - for cp in data.data.cp_data: - try: - chargepoint = data.data.cp_data[cp] - # Kein EV angesteckt - control_parameter = chargepoint.data.control_parameter - if (not chargepoint.data.get.plug_state or - # Kein EV, das Laden soll - # Kein EV, das auf das Ablaufen der Einschalt- oder Phasenumschaltverzögerung wartet - (control_parameter.state != ChargepointState.PERFORMING_PHASE_SWITCH and - control_parameter.state != ChargepointState.PHASE_SWITCH_DELAY and - control_parameter.state != ChargepointState.SWITCH_OFF_DELAY and - control_parameter.state != ChargepointState.SWITCH_ON_DELAY and - control_parameter.state != ChargepointState.NO_CHARGING_ALLOWED)): - continue - else: - break - except Exception: - log.exception("Fehler in der allgemeinen Ladepunkt-Klasse für Ladepunkt "+cp) - else: - data.data.counter_all_data.get_evu_counter().reset_pv_data() - except Exception: - log.exception("Fehler in der allgemeinen Ladepunkt-Klasse") - def get_cp_sum(self): """ ermittelt die aktuelle Leistung und Zählerstand von allen Ladepunkten. """ diff --git a/packages/control/consumer/consumer.py b/packages/control/consumer/consumer.py index 5a38263667..035a5eec03 100644 --- a/packages/control/consumer/consumer.py +++ b/packages/control/consumer/consumer.py @@ -80,13 +80,6 @@ def process_on_time(self): self.data.set.timestamp_wrote_last_on_time = now self.data.set.on_time += now - self.data.set.timestamp_wrote_last_on_time self.data.set.timestamp_wrote_last_on_time = now - if self.data.control_parameter.timestamp_charge_start is None: - self.data.control_parameter.timestamp_charge_start = timecheck.create_timestamp() - elif self.data.get.charge_state is False: - self.data.control_parameter.timestamp_charge_start = None - - def reset_timestamp_start(self): - self.data.control_parameter.timestamp_charge_start = None def reset_on_time(self): self.data.set.on_time = 0 @@ -456,7 +449,6 @@ def reset_wait_for_start(self): def midnight_handler(self): self.reset_chargemode_at_midnight() self.reset_wait_for_start() - self.reset_timestamp_start() self.reset_on_time() def reset_chargemode_at_midnight(self): diff --git a/packages/control/load.py b/packages/control/load.py new file mode 100644 index 0000000000..abd9bb666a --- /dev/null +++ b/packages/control/load.py @@ -0,0 +1,44 @@ + +import logging + +from control import data +from control.chargepoint.chargepoint import Chargepoint +from control.chargepoint.chargepoint_state import ChargepointState +from control.load_protocol import Load + + +log = logging.getLogger(__name__) + + +def no_charge() -> None: + """ Wenn keine EV angesteckt sind oder keine Verzögerungen aktiv sind, werden die Algorithmus-Werte + zurückgesetzt. + (dient der Robustheit) + """ + try: + for load in list(data.data.cp_data.values()) + list(data.data.consumer_data.values()): + try: + control_parameter = load.data.control_parameter + # Kein EV, das Laden soll + # Kein EV, das auf das Ablaufen der Einschalt- oder Phasenumschaltverzögerung wartet + if (control_parameter.state != ChargepointState.PERFORMING_PHASE_SWITCH and + control_parameter.state != ChargepointState.PHASE_SWITCH_DELAY and + control_parameter.state != ChargepointState.SWITCH_OFF_DELAY and + control_parameter.state != ChargepointState.SWITCH_ON_DELAY and + control_parameter.state != ChargepointState.NO_CHARGING_ALLOWED): + continue + else: + break + except Exception: + log.exception("Fehler beim Bereinigen der Werte für Ladepunkt/Verbraucher "+load.num) + else: + data.data.counter_all_data.get_evu_counter().reset_pv_data() + except Exception: + log.exception("Fehler beim Bereinigen der Werte") + + +def get_load_str(load: Load) -> str: + if isinstance(load, Chargepoint): + return f"Ladepunkt {load.num}" + else: + return f"Verbraucher {load.num}" diff --git a/packages/control/load_protocol.py b/packages/control/load_protocol.py index 856e7072fb..5a6b33de45 100644 --- a/packages/control/load_protocol.py +++ b/packages/control/load_protocol.py @@ -2,6 +2,7 @@ from typing import List, Optional, Protocol from control.chargepoint.control_parameter import ControlParameter +from helpermodules.timecheck import create_timestamp @dataclass @@ -47,6 +48,15 @@ class Load(Protocol): submode_changed: bool data: LoadData + def set_timestamp_charge_start(self) -> None: + # Beim Ladestart Timer laufen lassen, manche Fahrzeuge brauchen sehr lange. + # Nach dem Algorithmus setzen, sonst steht set current noch nicht fest. + if self.data.control_parameter.timestamp_charge_start is None: + if self.data.set.current_prev == 0 and self.data.set.current != 0: + self.data.control_parameter.timestamp_charge_start = create_timestamp() + elif self.data.set.current == 0: + self.data.control_parameter.timestamp_charge_start = None + def is_charging_stop_allowed(self) -> bool: ... diff --git a/packages/control/prepare.py b/packages/control/prepare.py index df9d80b2a8..e72620d8e6 100644 --- a/packages/control/prepare.py +++ b/packages/control/prepare.py @@ -3,7 +3,7 @@ import logging -from control import data +from control import data, load from modules.common.component_type import ComponentType @@ -38,7 +38,7 @@ def setup_algorithm(self) -> None: data.data.bat_all_data.setup_bat() data.data.consumer_all_data.get_consumer_sum() data.data.cp_all_data.get_cp_sum() - data.data.cp_all_data.no_charge() + load.no_charge() data.data.counter_all_data.set_home_consumption() except Exception: log.exception("Fehler im Prepare-Modul") diff --git a/packages/control/process.py b/packages/control/process.py index 95937c87cd..94c24bc5ad 100644 --- a/packages/control/process.py +++ b/packages/control/process.py @@ -80,10 +80,15 @@ def process_algorithm_results(self) -> None: try: self._update_state_consumer(consumer) if consumer.data.get.state_str is None: - if consumer.data.get.charge_state: - consumer.data.get.state_str = "Verbraucher läuft." + if consumer.data.usage.type == ConsumerUsage.METER_ONLY: + consumer.data.get.state_str = "Messwerte des Verbrauchers werden erfasst." + elif consumer.data.usage.type == ConsumerUsage.SELF_CONTROLLED: + consumer.data.get.state_str = "Messwerte werden an den Verbraucher übermittelt." else: - consumer.data.get.state_str = "Verbraucher wird gestartet... " + if consumer.data.get.charge_state: + consumer.data.get.state_str = "Verbraucher läuft." + else: + consumer.data.get.state_str = "Verbraucher wird gestartet... " modules_threads.append(self._start_consumer(consumer)) except Exception: @@ -168,17 +173,21 @@ def _start_charging(self, chargepoint: chargepoint.Chargepoint) -> Thread: name=f"set current cp{chargepoint.chargepoint_module.config.id}") def _update_state_consumer(self, consumer: Consumer) -> None: + control_parameter = consumer.data.control_parameter + if consumer.data.set.switch_interval_elapsed is False: log.debug("Intervall für neuen Schaltbefehl nicht abgelaufen.") consumer.data.set.current = consumer.data.set.current_prev else: consumer.data.set.current = round(consumer.data.set.current, 2) - if consumer.data.set.current != 0 and consumer.data.control_parameter.state not in CHARGING_STATES: - consumer.data.control_parameter.state = ChargepointState.CHARGING_ALLOWED + if consumer.data.set.current != 0 and control_parameter.state not in CHARGING_STATES: + control_parameter.state = ChargepointState.CHARGING_ALLOWED + if control_parameter.state != ChargepointState.NO_CHARGING_ALLOWED or consumer.data.set.current != 0: + consumer.set_timestamp_charge_start() consumer.data.set.power = consumer.data.set.current * \ voltages_mean(consumer.data.get.voltages) * consumer.data.config.connected_phases log.info(f"Verbraucher{consumer.num}: set current {consumer.data.set.current} A, " - f"state {ChargepointState(consumer.data.control_parameter.state).name}") + f"state {ChargepointState(control_parameter.state).name}") def _start_consumer(self, consumer: Consumer) -> Thread: if consumer.data.set.current != consumer.data.set.current_prev: