Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 3 additions & 18 deletions packages/control/algorithm/surplus_controlled.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,7 @@ def set_surplus_current(self) -> None:
for mode_tuple, counter in common.mode_and_counter_generator(CONSIDERED_CHARGE_MODES_SURPLUS):
preferenced_chargepoints, preferenced_cps_without_set_current = get_preferenced_chargepoint_charging(
get_chargepoints_by_mode_and_counter(mode_tuple, 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)
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(preferenced_chargepoints, mode_tuple, counter)
if preferenced_cps_without_set_current:
for cp in preferenced_cps_without_set_current:
cp.data.set.current = cp.data.set.target_current
Expand All @@ -46,7 +41,6 @@ def set_surplus_current(self) -> None:

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}")
Expand All @@ -58,12 +52,11 @@ def _set(self,
missing_currents,
voltages_mean(cp.data.get.voltages),
counter,
cp,
feed_in=feed_in_yield
cp
)
cp.data.control_parameter.limit = limit
available_for_cp = common.available_current_for_cp(cp, counts, available_currents, missing_currents)
if counter.get_control_range_state(feed_in_yield) == ControlRangeState.MIDDLE:
if counter.get_control_range_state() == ControlRangeState.MIDDLE:
pv_charging = data.data.general_data.data.chargemode_config.pv_charging
dif_to_old_current = available_for_cp + cp.data.set.target_current - cp.data.set.current_prev
# Wenn die Differenz zwischen altem und neuem Soll-Strom größer als der Regelbereich ist, trotzdem
Expand Down Expand Up @@ -101,14 +94,6 @@ def _set_loadmangement_message(self,
chargepoint.set_state_and_log(f"Es kann nicht mit der vorgegebenen Stromstärke geladen werden"
f"{limit.message}")

# tested
def filter_by_feed_in_limit(self, chargepoints: List[Chargepoint]) -> Tuple[List[Chargepoint], List[Chargepoint]]:
cp_with_feed_in = list(filter(lambda cp: cp.data.set.charge_template.data.chargemode.
pv_charging.feed_in_limit is True, chargepoints))
cp_without_feed_in = list(filter(lambda cp: cp.data.set.charge_template.data.chargemode.
pv_charging.feed_in_limit is False, chargepoints))
return cp_with_feed_in, cp_without_feed_in

def _fix_deviating_evse_current(self, chargepoint: Chargepoint) -> float:
"""Wenn Autos nicht die volle Ladeleistung nutzen, wird unnötig eingespeist. Dann kann um den noch nicht
genutzten Soll-Strom hochgeregelt werden. Wenn Fahrzeuge entgegen der Norm mehr Ladeleistung beziehen, als
Expand Down
36 changes: 9 additions & 27 deletions packages/control/algorithm/surplus_controlled_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,29 +30,6 @@ def mock_cp3() -> Chargepoint:
return Chargepoint(3, None)


@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])),
pytest.param(False, False, False, ([], [mock_cp1, mock_cp2, mock_cp3]))])
def test_filter_by_feed_in_limit(feed_in_limit_1: bool,
feed_in_limit_2: bool,
feed_in_limit_3: bool,
expected_sorted: int):
# setup
def setup_cp(cp: Chargepoint, feed_in_limit: bool) -> Chargepoint:
cp.data = ChargepointData()
cp.data.set.charge_template.data.chargemode.pv_charging.feed_in_limit = feed_in_limit
return cp

cp1 = setup_cp(mock_cp1, feed_in_limit_1)
cp2 = setup_cp(mock_cp2, feed_in_limit_2)
cp3 = setup_cp(mock_cp3, feed_in_limit_3)
# execution
cp_with_feed_in, cp_without_feed_in = SurplusControlled().filter_by_feed_in_limit([cp1, cp2, cp3])
# evaluation
assert (cp_with_feed_in, cp_without_feed_in) == expected_sorted


@pytest.mark.parametrize("new_current, expected_current",
[
pytest.param(7, 10),
Expand Down Expand Up @@ -129,16 +106,21 @@ 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: Chargepoint,
mock_cp2: Chargepoint):
# setup
cp_mapping = {1: mock_cp1, 2: mock_cp2}
expected_chargepoints = [cp_mapping[i] for i in expected_cp_indices]

def setup_cp(cp: Chargepoint, submode: str) -> Chargepoint:
cp.data.set.charging_ev_data = Ev(0)
cp.data.control_parameter.chargemode = Chargemode.PV_CHARGING
Expand Down
23 changes: 13 additions & 10 deletions packages/control/counter.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,13 @@ def calc_raw_surplus(self):
ranged_surplus = surplus + self._control_range_offset()
return ranged_surplus

def get_control_range_state(self, feed_in_yield: int) -> ControlRangeState:
def get_control_range_state(self) -> ControlRangeState:
control_range_low = data.data.general_data.data.chargemode_config.pv_charging.control_range[0]
control_range_high = data.data.general_data.data.chargemode_config.pv_charging.control_range[1]
if data.data.general_data.data.chargemode_config.pv_charging.feed_in_limit:
feed_in_yield = data.data.general_data.data.chargemode_config.feed_in_yield
else:
feed_in_yield = 0
surplus = data.data.counter_all_data.get_evu_counter().data.get.power + feed_in_yield
if control_range_low > surplus:
return ControlRangeState.BELOW
Expand Down Expand Up @@ -270,8 +274,8 @@ def calc_switch_on_power(self, chargepoint: Chargepoint) -> Tuple[float, float]:
control_parameter = chargepoint.data.control_parameter
pv_config = data.data.general_data.data.chargemode_config.pv_charging

if chargepoint.data.set.charge_template.data.chargemode.pv_charging.feed_in_limit:
threshold = pv_config.feed_in_yield
if data.data.general_data.data.chargemode_config.pv_charging.feed_in_limit:
threshold = data.data.general_data.data.chargemode_config.feed_in_yield
else:
threshold = pv_config.switch_on_threshold*control_parameter.phases
return surplus, threshold
Expand All @@ -280,8 +284,7 @@ def switch_on_threshold_reached(self, chargepoint: Chargepoint) -> None:
try:
message = None
control_parameter = chargepoint.data.control_parameter
feed_in_limit = chargepoint.data.set.charge_template.data.chargemode.pv_charging.\
feed_in_limit
feed_in_limit = data.data.general_data.data.chargemode_config.pv_charging.feed_in_limit
pv_config = data.data.general_data.data.chargemode_config.pv_charging
timestamp_switch_on_off = control_parameter.timestamp_switch_on_off

Expand All @@ -305,7 +308,7 @@ def switch_on_threshold_reached(self, chargepoint: Chargepoint) -> None:
message = self.SWITCH_ON_WAITING.format(timecheck.convert_timestamp_delta_to_time_string(
timestamp_switch_on_off, pv_config.switch_on_delay))
if feed_in_limit:
message += "Die Einspeisegrenze wird berücksichtigt."
message += " Die Einspeisegrenze wird berücksichtigt."
control_parameter.state = ChargepointState.SWITCH_ON_DELAY
else:
# Einschaltschwelle nicht erreicht
Expand Down Expand Up @@ -343,8 +346,8 @@ def switch_on_timer_expired(self, chargepoint: Chargepoint) -> None:
msg = self.SWITCH_ON_EXPIRED.format(pv_config.switch_on_threshold)
control_parameter.state = ChargepointState.WAIT_FOR_USING_PHASES

if chargepoint.data.set.charge_template.data.chargemode.pv_charging.feed_in_limit:
feed_in_yield = pv_config.feed_in_yield
if pv_config.feed_in_limit:
feed_in_yield = data.data.general_data.data.chargemode_config.feed_in_yield
else:
feed_in_yield = 0
ev_template = charging_ev_data.ev_template
Expand Down Expand Up @@ -391,11 +394,11 @@ def switch_off_check_timer(self, chargepoint: Chargepoint) -> None:
def calc_switch_off_threshold(self, chargepoint: Chargepoint) -> float:
pv_config = data.data.general_data.data.chargemode_config.pv_charging
control_parameter = chargepoint.data.control_parameter
if chargepoint.data.set.charge_template.data.chargemode.pv_charging.feed_in_limit:
if pv_config.feed_in_limit:
# Der EVU-Überschuss muss ggf um die Einspeisegrenze bereinigt werden.
# Wnn die Leistung nicht Einspeisegrenze + Einschaltschwelle erreicht, darf die Ladung nicht pulsieren.
# Abschaltschwelle um Einschaltschwelle reduzieren.
threshold = (-data.data.general_data.data.chargemode_config.pv_charging.feed_in_yield
threshold = (-data.data.general_data.data.chargemode_config.feed_in_yield
+ pv_config.switch_on_threshold*control_parameter.phases)
else:
threshold = pv_config.switch_off_threshold
Expand Down
4 changes: 2 additions & 2 deletions packages/control/counter_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ class Params:
-681, 15000, 1652683250.0, ChargepointState.SWITCH_ON_DELAY,
Counter.SWITCH_ON_FALLEN_BELOW.format(1500), None, 0),
Params("Feed_in_limit, Timer starten", True, 0, 15001, 15000, None, ChargepointState.NO_CHARGING_ALLOWED,
Counter.SWITCH_ON_WAITING.format("30 Sek."), 1652683252.0, 1500),
Counter.SWITCH_ON_WAITING.format("30 Sek.")+" Die Einspeisegrenze wird berücksichtigt.", 1652683252.0, 1500),
Params("Feed_in_limit, Einschaltschwelle nicht erreicht", True, 0, 14999,
15000, None, ChargepointState.NO_CHARGING_ALLOWED, Counter.SWITCH_ON_NOT_EXCEEDED.format(1500), None, 0),
Params("Feed_in_limit, Einschaltschwelle läuft", True, 1500, 15001,
Expand All @@ -141,7 +141,7 @@ def test_switch_on_threshold_reached(params: Params, caplog, general_data_fixtur
cp.data.control_parameter.state = params.state
cp.data.control_parameter.timestamp_switch_on_off = params.timestamp_switch_on_off
ev.data.charge_template = ChargeTemplate()
ev.data.charge_template.data.chargemode.pv_charging.feed_in_limit = params.feed_in_limit
data.data.general_data.data.chargemode_config.pv_charging.feed_in_limit = params.feed_in_limit
cp.data.set.charging_ev_data = ev
mock_calc_switch_on_power = Mock(return_value=[params.surplus, params.threshold])
monkeypatch.setattr(Counter, "calc_switch_on_power", mock_calc_switch_on_power)
Expand Down
1 change: 0 additions & 1 deletion packages/control/ev/charge_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ class InstantCharging:
class PvCharging:
dc_min_current: float = 145
dc_min_soc_current: float = 145
feed_in_limit: bool = False
limit: Limit = field(default_factory=limit_factory)
min_current: int = 0
min_soc_current: int = 10
Expand Down
8 changes: 4 additions & 4 deletions packages/control/ev/ev.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,8 @@ def _check_phase_switch_conditions(self,
phases_in_use = control_parameter.phases
pv_config = data.data.general_data.data.chargemode_config.pv_charging
max_phases_ev = self.ev_template.data.max_phases
if charge_template.data.chargemode.pv_charging.feed_in_limit:
feed_in_yield = pv_config.feed_in_yield
if pv_config.feed_in_limit:
feed_in_yield = data.data.general_data.data.chargemode_config.feed_in_yield
else:
feed_in_yield = 0
all_surplus = data.data.counter_all_data.get_evu_counter().get_usable_surplus(feed_in_yield)
Expand Down Expand Up @@ -311,8 +311,8 @@ def auto_phase_switch(self,
phases_to_use = control_parameter.phases
phases_in_use = control_parameter.phases
pv_config = data.data.general_data.data.chargemode_config.pv_charging
if charge_template.data.chargemode.pv_charging.feed_in_limit:
feed_in_yield = pv_config.feed_in_yield
if pv_config.feed_in_limit:
feed_in_yield = data.data.general_data.data.chargemode_config.feed_in_yield
else:
feed_in_yield = 0
all_surplus = data.data.counter_all_data.get_evu_counter().get_usable_surplus(feed_in_yield)
Expand Down
6 changes: 4 additions & 2 deletions packages/control/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ class PvCharging:
"topic": "chargemode_config/pv_charging/bat_power_reserve_active"})
control_range: List = field(default_factory=control_range_factory, metadata={
"topic": "chargemode_config/pv_charging/control_range"})
feed_in_yield: int = field(default=15000, metadata={
"topic": "chargemode_config/pv_charging/feed_in_yield"})
feed_in_limit: bool = field(default=False, metadata={
"topic": "chargemode_config/pv_charging/feed_in_limit"})
phase_switch_delay: int = field(default=7, metadata={
"topic": "chargemode_config/pv_charging/phase_switch_delay"})
bat_power_discharge: int = field(default=1500, metadata={
Expand Down Expand Up @@ -57,6 +57,8 @@ def pv_charging_factory() -> PvCharging:

@dataclass
class ChargemodeConfig:
feed_in_yield: int = field(default=15000, metadata={
"topic": "chargemode_config/feed_in_yield"})
pv_charging: PvCharging = field(default_factory=pv_charging_factory)
unbalanced_load_limit: int = field(
default=18, metadata={"topic": "chargemode_config/unbalanced_load_limit"})
Expand Down
17 changes: 7 additions & 10 deletions packages/control/loadmanagement.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ class Loadmanagement:
def get_available_currents(self,
missing_currents: List[float],
counter: Counter,
cp: Chargepoint,
feed_in: int = 0) -> Tuple[List[float], LoadmanagementLimit]:
cp: Chargepoint) -> Tuple[List[float], LoadmanagementLimit]:
raw_currents_left = counter.data.set.raw_currents_left
try:
available_currents, limit = self._limit_by_dimming_via_direct_control(missing_currents, cp)
Expand All @@ -36,7 +35,7 @@ def get_available_currents(self,
limit = new_limit if new_limit.limiting_value is not None else limit

available_currents, new_limit = self._limit_by_power(
counter, available_currents, voltages_mean(cp.data.get.voltages), counter.data.set.raw_power_left, feed_in)
counter, available_currents, voltages_mean(cp.data.get.voltages), counter.data.set.raw_power_left)
limit = new_limit if new_limit.limiting_value is not None else limit

if f"counter{counter.num}" == data.data.counter_all_data.get_evu_counter_str():
Expand All @@ -50,8 +49,7 @@ def get_available_currents_surplus(self,
missing_currents: List[float],
cp_voltage: float,
counter: Counter,
cp: Chargepoint,
feed_in: int = 0) -> Tuple[List[float], LoadmanagementLimit]:
cp: Chargepoint) -> Tuple[List[float], LoadmanagementLimit]:
raw_currents_left = counter.data.set.raw_currents_left
available_currents, limit = self._limit_by_dimming_via_direct_control(missing_currents, cp)

Expand All @@ -62,7 +60,7 @@ def get_available_currents_surplus(self,
limit = new_limit if new_limit.limiting_value is not None else limit

available_currents, new_limit = self._limit_by_power(
counter, available_currents, cp_voltage, counter.data.set.surplus_power_left, feed_in)
counter, available_currents, cp_voltage, counter.data.set.surplus_power_left)
limit = new_limit if new_limit.limiting_value is not None else limit

if f"counter{counter.num}" == data.data.counter_all_data.get_evu_counter_str():
Expand Down Expand Up @@ -96,15 +94,14 @@ def _limit_by_power(self,
counter: Counter,
available_currents: List[float],
cp_voltage: float,
raw_power_left: Optional[float],
feed_in: Optional[float]) -> Tuple[List[float], LoadmanagementLimit]:
raw_power_left: Optional[float]) -> Tuple[List[float], LoadmanagementLimit]:
# Mittelwert der Spannungen verwenden, um Phasenverdrehung zu kompensieren
# (Probleme bei einphasig angeschlossenen Wallboxen)
currents = available_currents.copy()
limit = LoadmanagementLimit(None, None)
if raw_power_left:
if feed_in:
raw_power_left = raw_power_left - feed_in
if data.data.general_data.data.chargemode_config.pv_charging.feed_in_limit:
raw_power_left = raw_power_left - data.data.general_data.data.chargemode_config.feed_in_yield
log.debug(f"Verbleibende Leistung unter Berücksichtigung der Einspeisegrenze: {raw_power_left}W")
if sum([c * cp_voltage for c in available_currents]) > raw_power_left:
for i in range(0, 3):
Expand Down
2 changes: 1 addition & 1 deletion packages/control/loadmanagement_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def test_limit_by_power(available_currents: List[float],
counter_name_mock = Mock(return_value=COUNTER_NAME)
monkeypatch.setattr(loadmanagement, "get_component_name_by_id", counter_name_mock)
# evaluation
currents = Loadmanagement()._limit_by_power(Counter(0), available_currents, 230, raw_power_left, None)
currents = Loadmanagement()._limit_by_power(Counter(0), available_currents, 230, raw_power_left)

# assertion
assert currents == expected_currents
Expand Down
Loading