Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix pv charge regulation #1406

Closed
wants to merge 8 commits into from
108 changes: 108 additions & 0 deletions packages/control/algorithm/algorithm_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
from unittest.mock import Mock
import pytest

from control import data
from control.algorithm.algorithm import Algorithm
from control.chargemode import Chargemode
from control.chargepoint.chargepoint import Chargepoint
from control.chargepoint.chargepoint_state import ChargepointState
from control.counter import Counter
from control.general import General


@pytest.fixture(autouse=True)
def data_fixture() -> None:
data.data_init(Mock())
data.data.general_data = General()
data.data.cp_data = {"cp2": Chargepoint(2, None)}
data.data.counter_data = {"counter0": Counter(0)}
data.data.counter_all_data.data.get.hierarchy = [
{"id": 0,
"type": "counter",
"children": [
{"id": 1,
"type": "inverter",
"children": []},
{"id": 2,
"type": "cp",
"children": []}]}]


@pytest.mark.parametrize("control_range, evu_power, evu_currents, evu_voltages, cp_power, cp_currents",
[pytest.param(
[-230, 0], -84.08, [-2.42, 4.69, -2.66], [239, 237.8, 237.6], 1823.86, [7.81, -0.06, 0.06],
id="Einspeisung, im Regelbereich"),
pytest.param(
[-230, 0], 41.96, [-2.4, 5.22, -2.64], [238.7, 237.7, 237.7], 1939.72, [8.31, -0.06, 0.06],
id="Einspeisung, über Regelbereich"),
pytest.param(
[-230, 0], -690, [-1, -1, -1], [230, 230, 230], 1610, [7, 0, 0],
id="Einspeisung bei 230V Spannung, unter Regelbereich"),
pytest.param(
[-230, 0], -115, [0, -0.5, 0], [230, 230, 230], 2185, [9.5, 0, 0],
id="Einspeisung bei 230V Spannung, im Regelbereich"),
pytest.param(
[-230, 0], -115, [0, -0.49, 0], [235, 235, 235], 2185, [9.3, 0, 0],
id="Einspeisung bei 235V Spannung, im Regelbereich"),
pytest.param(
[-230, 0], 235, [0, 1, 0], [235, 235, 235], 2535, [10.87, 0, 0],
id="Einspeisung bei 235V Spannung, über Regelbereich"),
pytest.param(
[0, 230], -230, [0, -1, 0], [230, 230, 230], 2070, [9, 0, 0],
id="Bezug bei 230V Spannung, unter Regelbereich"),
pytest.param(
[0, 230], 115, [0, 0.5, 0], [230, 230, 230], 2415, [10.5, 0, 0],
id="Bezug bei 230V Spannung, im Regelbereich"),
pytest.param(
[0, 230], 115, [0, 0.49, 0], [235, 235, 235], 2415, [10.3, 0, 0],
id="Bezug bei 235V Spannung, im Regelbereich"),
pytest.param(
[0, 230], 705, [1, 1, 1], [235, 235, 235], 3005, [12.8, 0, 0],
id="Bezug bei 235V Spannung, über Regelbereich"),
pytest.param(
[-115, 115], -230, [0, -1, 0], [230, 230, 230], 2070, [9, 0, 0],
id="Ausgeglichen bei 230V Spannung, unter Regelbereich"),
pytest.param(
[-115, 115], 0, [0, 0, 0], [230, 230, 230], 2300, [10, 0, 0],
id="Ausgeglichen bei 230V Spannung, im Regelbereich"),
pytest.param(
[-115, 115], 0, [0, 0, 0], [235, 235, 235], 2300, [9.8, 0, 0],
id="Ausgeglichen bei 235V Spannung, im Regelbereich"),
pytest.param(
[-115, 115], 235, [0, 1, 0], [235, 235, 235], 2535, [10.8, 0, 0],
id="Ausgeglichen bei 235V Spannung, über Regelbereich")
]
)
def test_calc_current(control_range, evu_power, evu_currents, evu_voltages, cp_power, cp_currents, monkeypatch):
# setup
data.data.general_data.data.chargemode_config.pv_charging.control_range = control_range
cp = data.data.cp_data["cp2"]
cp.data.config.phase_1 = 2
cp.data.control_parameter.state = ChargepointState.CHARGING_ALLOWED
cp.data.control_parameter.chargemode = Chargemode.PV_CHARGING
cp.data.control_parameter.submode = Chargemode.PV_CHARGING
cp.data.control_parameter.phases = 1
cp.data.control_parameter.required_current = 6
cp.data.control_parameter.required_currents = [0, 6, 0]
cp.data.set.charging_ev = 0
cp.data.get.currents = cp_currents
cp.data.get.power = cp_power
counter = data.data.counter_data["counter0"]
counter.data.config.max_currents = [35, 35, 35]
counter.data.config.max_total_power = 24000
counter.data.get.currents = evu_currents
counter.data.get.voltages = evu_voltages
counter.data.get.power = evu_power
counter._set_power_left()
counter._set_current_left()
a = Algorithm()

# execution
a.calc_current()

# evaluation
average_voltage = sum(cp.data.get.voltages)/len(cp.data.get.voltages)
initial_power_left = cp.data.get.power - counter.data.get.power
new_power = cp.data.set.current * average_voltage
# calculated surplus is in control range
assert control_range[0] < int(new_power - initial_power_left) < control_range[1]
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ def test_start_pv_delay(all_cp_pv_charging_3p, all_cp_not_charging, monkeypatch)
assert data.data.cp_data[
"cp5"].data.control_parameter.timestamp_switch_on_off is None
assert data.data.counter_data["counter0"].data.set.raw_power_left == 31775
assert data.data.counter_data["counter0"].data.set.surplus_power_left == 9660
assert data.data.counter_data["counter0"].data.set.surplus_power_left == 9890
assert data.data.counter_data["counter0"].data.set.reserved_surplus == 9000


Expand Down Expand Up @@ -172,7 +172,7 @@ def test_pv_delay_expired(all_cp_pv_charging_3p, all_cp_not_charging, monkeypatc
assert data.data.cp_data[
"cp5"].data.control_parameter.timestamp_switch_on_off is None
assert data.data.counter_data["counter0"].data.set.raw_power_left == 24300
assert data.data.counter_data["counter0"].data.set.surplus_power_left == 2185
assert data.data.counter_data["counter0"].data.set.surplus_power_left == 2415
assert data.data.counter_data["counter0"].data.set.reserved_surplus == 0


Expand Down Expand Up @@ -210,7 +210,7 @@ def test_pv_delay_expired(all_cp_pv_charging_3p, all_cp_not_charging, monkeypatc
expected_current_cp4=6,
expected_current_cp5=6,
expected_raw_power_left=5635,
expected_surplus_power_left=-16480.0,
expected_surplus_power_left=-16250.0,
expected_reserved_surplus=0,
expected_released_surplus=11040),
]
Expand Down Expand Up @@ -246,7 +246,7 @@ def test_surplus(params: ParamsSurplus, all_cp_pv_charging_3p, all_cp_charging_3
expected_current_cp4=6,
expected_current_cp5=6,
expected_raw_power_left=17400,
expected_surplus_power_left=-4715,
expected_surplus_power_left=-4485,
expected_reserved_surplus=0,
expected_released_surplus=0),
ParamsPhaseSwitch(name="phase switch 1p->3p",
Expand Down
17 changes: 6 additions & 11 deletions packages/control/counter.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,14 +175,16 @@ def _set_power_left(self) -> None:
def update_values_left(self, diffs) -> None:
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(diffs) * 230
average_voltage = sum(self.data.get.voltages)/len(self.data.get.voltages)
self.data.set.raw_power_left -= sum(diffs) * average_voltage
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')

def update_surplus_values_left(self, diffs) -> None:
self.data.set.raw_currents_left = list(map(operator.sub, self.data.set.raw_currents_left, diffs))
if self.data.set.surplus_power_left:
self.data.set.surplus_power_left -= sum(diffs) * 230
average_voltage = sum(self.data.get.voltages)/len(self.data.get.voltages)
self.data.set.surplus_power_left -= sum(diffs) * average_voltage
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')

Expand Down Expand Up @@ -228,15 +230,8 @@ def _control_range_offset(self):
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]
control_range_center = control_range_high - (control_range_high - control_range_low) / 2
control_range_state = self.get_control_range_state(0)
if control_range_state == ControlRangeState.BELOW:
range_offset = abs(control_range_center)
elif control_range_state == ControlRangeState.ABOVE:
range_offset = - abs(control_range_center)
else:
range_offset = 0
log.debug(f"Anpassen des Regelbereichs {range_offset}W")
return range_offset
log.debug(f"Anpassen des Regelbereichs {control_range_center}W")
return control_range_center

def get_usable_surplus(self, feed_in_yield: float) -> float:
# verbleibender EVU-Überschuss unter Berücksichtigung der Einspeisegrenze und Speicherleistung
Expand Down
8 changes: 4 additions & 4 deletions packages/control/counter_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,12 @@ def test_switch_on_threshold_reached(params: Params, caplog, general_data_fixtur


@pytest.mark.parametrize("control_range, evu_power, expected_range_offset",
[pytest.param([0, 230], 200, 0, id="Bezug, im Regelbereich"),
pytest.param([0, 230], 290, -115, id="Bezug, über Regelbereich"),
[pytest.param([0, 230], 200, 115, id="Bezug, im Regelbereich"),
pytest.param([0, 230], 290, 115, id="Bezug, über Regelbereich"),
pytest.param([0, 230], -100, 115, id="Bezug, unter Regelbereich"),
pytest.param([-230, 0], -104, 0, id="Einspeisung, im Regelbereich"),
pytest.param([-230, 0], -104, -115, id="Einspeisung, im Regelbereich"),
pytest.param([-230, 0], 80, -115, id="Einspeisung, über Regelbereich"),
pytest.param([-230, 0], -300, 115, id="Einspeisung, unter Regelbereich"),
pytest.param([-230, 0], -300, -115, id="Einspeisung, unter Regelbereich"),
],
)
def test_control_range(control_range, evu_power, expected_range_offset, general_data_fixture, monkeypatch):
Expand Down
11 changes: 7 additions & 4 deletions packages/control/loadmanagement.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ def get_available_currents(self,
counter: Counter,
feed_in: int = 0) -> Tuple[List[float], Optional[LimitingValue]]:
raw_currents_left = counter.data.set.raw_currents_left
average_voltage = sum(counter.data.get.voltages)/len(counter.data.get.voltages)
available_currents, limit = self._limit_by_current(missing_currents, raw_currents_left)
available_currents, limit_power = self._limit_by_power(
available_currents, counter.data.set.raw_power_left, feed_in)
available_currents, average_voltage, counter.data.set.raw_power_left, feed_in)
if limit_power is not None:
limit = limit_power
if f"counter{counter.num}" == data.data.counter_all_data.get_evu_counter_str():
Expand All @@ -33,9 +34,10 @@ def get_available_currents_surplus(self,
counter: Counter,
feed_in: int = 0) -> Tuple[List[float], Optional[LimitingValue]]:
raw_currents_left = counter.data.set.raw_currents_left
average_voltage = sum(counter.data.get.voltages)/len(counter.data.get.voltages)
available_currents, limit = self._limit_by_current(missing_currents, raw_currents_left)
available_currents, limit_power = self._limit_by_power(
available_currents, counter.data.set.surplus_power_left, feed_in)
available_currents, average_voltage, counter.data.set.surplus_power_left, feed_in)
if limit_power is not None:
limit = limit_power
if f"counter{counter.num}" == data.data.counter_all_data.get_evu_counter_str():
Expand All @@ -61,6 +63,7 @@ def _limit_by_unbalanced_load(self,
# tested
def _limit_by_power(self,
available_currents: List[float],
average_voltage: float,
raw_power_left: Optional[float],
feed_in: Optional[float]) -> Tuple[List[float], Optional[LimitingValue]]:
currents = available_currents.copy()
Expand All @@ -69,10 +72,10 @@ def _limit_by_power(self,
if feed_in:
raw_power_left = raw_power_left - feed_in
log.debug(f"Verbleibende Leistung unter Berücksichtigung der Einspeisegrenze: {raw_power_left}W")
if sum(available_currents)*230 > raw_power_left:
if sum(available_currents)*average_voltage > raw_power_left:
for i in range(0, 3):
# Am meisten belastete Phase trägt am meisten zur Leistungsreduktion bei.
currents[i] = available_currents[i] / sum(available_currents) * raw_power_left / 230
currents[i] = available_currents[i] / sum(available_currents) * raw_power_left / average_voltage
log.debug(f"Leistungsüberschreitung auf {raw_power_left}W korrigieren: {available_currents}")
limit = LimitingValue.POWER
return currents, limit
Expand Down
13 changes: 7 additions & 6 deletions packages/control/loadmanagement_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@


@pytest.mark.parametrize(
"available_currents, raw_power_left, expected_currents",
"available_currents, voltage, raw_power_left, expected_currents",
[
pytest.param([5, 10, 15], 6900, ([5, 10, 15], None)),
pytest.param([5, 10, 25], 1000, ([0.5434782608695652, 1.0869565217391304,
pytest.param([5, 10, 15], 230, 6900, ([5, 10, 15], None)),
pytest.param([5, 10, 25], 230, 1000, ([0.5434782608695652, 1.0869565217391304,
2.717391304347826], LimitingValue.POWER)),
pytest.param([5, 10, 25], 5000, ([2.717391304347826, 5.434782608695652,
pytest.param([5, 10, 25], 230, 5000, ([2.717391304347826, 5.434782608695652,
13.58695652173913], LimitingValue.POWER)),
])
def test_limit_by_power(available_currents: List[float], raw_power_left: float, expected_currents: List[float]):
def test_limit_by_power(available_currents: List[float], voltage: float, raw_power_left: float,
expected_currents: List[float]):
# setup & evaluation
currents = Loadmanagement()._limit_by_power(available_currents, raw_power_left, None)
currents = Loadmanagement()._limit_by_power(available_currents, voltage, raw_power_left, None)

# assertion
assert currents == expected_currents
Expand Down