Skip to content
Merged
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
3 changes: 2 additions & 1 deletion packages/control/prepare.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ def setup_algorithm(self) -> None:
""" bereitet die Daten für den Algorithmus vor und startet diesen.
"""
try:
# vor PV, greift auf IO-Aktion stepwise_control zu
data.data.io_actions.setup()
data.data.pv_all_data.calc_power_for_all_components()
data.data.bat_all_data.calc_power_for_all_components()
for cp in data.data.cp_data.values():
Expand All @@ -34,7 +36,6 @@ def setup_algorithm(self) -> None:
data.data.cp_all_data.get_cp_sum()
data.data.cp_all_data.no_charge()
data.data.counter_all_data.set_home_consumption()
data.data.io_actions.setup()
except Exception:
log.exception("Fehler im Prepare-Modul")
data.data.print_all()
8 changes: 5 additions & 3 deletions packages/control/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from modules.common.fault_state_level import FaultStateLevel
from modules.io_actions.controllable_consumers.dimming.api_io import DimmingIo
from modules.io_actions.controllable_consumers.dimming_direct_control.api import DimmingDirectControl
from modules.io_actions.generator_systems.stepwise_control.api_eebus import StepwiseControlEebus
from modules.io_actions.generator_systems.stepwise_control.api_io import StepwiseControlIo

log = logging.getLogger(__name__)
Expand Down Expand Up @@ -83,16 +84,17 @@ def process_algorithm_results(self) -> None:
data.data.io_states[f"io_states{d['id']}"].data.set.digital_output[d["digital_output"]] = (
not action.dimming_active() # active output (True) if no dimming
)
if isinstance(action, StepwiseControlIo):
if isinstance(action, (StepwiseControlEebus, StepwiseControlIo)):
# check if passthrough is enabled
if action.config.configuration.passthrough_enabled:
if (action.config.configuration.passthrough_enabled and
action.config.configuration.io_output_device is not None):
# find output pattern by value
for pattern in action.config.configuration.output_pattern:
if pattern["value"] == action.control_stepwise()[0]:
# set digital outputs according to matching output_pattern
for output in pattern["matrix"].keys():
data.data.io_states[
f"io_states{action.config.configuration.io_device}"
f"io_states{action.config.configuration.io_output_device}"
].data.set.digital_output[output] = pattern["matrix"][output]
for io in data.data.system_data.values():
if isinstance(io, AbstractIoDevice):
Expand Down
16 changes: 15 additions & 1 deletion packages/helpermodules/update_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@

class UpdateConfig:

DATASTORE_VERSION = 133
DATASTORE_VERSION = 134

valid_topic = [
"^openWB/bat/config/bat_control_activated$",
Expand Down Expand Up @@ -3421,3 +3421,17 @@ def upgrade(topic: str, payload) -> None:
return {topic: provider}
self._loop_all_received_topics(upgrade)
self._append_datastore_version(133)

def upgrade_datastore_134(self) -> None:
def upgrade(topic: str, payload) -> Optional[dict]:
if re.search("openWB/io/action/[0-9]+/config", topic) is not None:
config = decode_payload(payload)
if config.get("type") == "stepwise_control":
if config["configuration"]["passthrough_enabled"] is True:
if config["configuration"].get("io_output_device") is None:
config["configuration"]["io_output_device"] = config["configuration"].get("io_device")
else:
config["configuration"]["io_output_device"] = None
return {topic: config}
self._loop_all_received_topics(upgrade)
self._append_datastore_version(134)
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,34 @@ def __init__(self, config: StepwiseControlSetup):
)
super().__init__()

def get_step(self) -> float:
step: float = 0
try:
max_output_inverter: float = 0
for inverter in self.config.configuration.devices:
if data.data.pv_data[f"pv{inverter['id']}"].data.config.max_ac_out == 0:
msg = "Maximale Ausgangsleistung des Wechselrichters muss im Lastmanagement konfiguriert werden."
control_command_log.error(msg)
raise ValueError(msg)
max_output_inverter += data.data.pv_data[f"pv{inverter['id']}"].data.config.max_ac_out
percentage = self.lpp_value / max_output_inverter
except ZeroDivisionError:
msg = ("Bitte unter Konfiguration -> Lastmanagement die maximale Ausgangsleistung"
" des Wechselrichters angeben.")
log.exception(msg)
control_command_log.info(msg)
percentage = 0
except ValueError:
percentage = 0
except Exception:
log.exception("Fehler beim Berechnen des Steuerungsschritts.")
percentage = 0
for s in [0, 0.3, 0.6, 1.0]:
if percentage <= s:
step = s
break
return step

def setup(self) -> None:
with ModifyLoglevelContext(control_command_log, logging.DEBUG):
if check_fault_state_io_device(self.config.configuration.io_device):
Expand All @@ -57,34 +85,20 @@ def setup(self) -> None:
].data.get.digital_input_prev[DigitalInputMapping.LPP_ACTIVE.name]
changed = True if self.lpp_value != lpp_value_prev or self.lpp_active != lpp_active_prev else False

max_output_inverter = 0
for inverter in self.config.configuration.devices:
max_output_inverter += data.data.pv_data[f"pv{inverter['id']}"].data.config.max_ac_out

if self.lpp_active:
try:
self.step = self.lpp_value / max_output_inverter
except ZeroDivisionError:
msg = ("Bitte unter Konfiguration -> Lastmanagement die maximale Ausgangsleistung"
" des Wechselrichters angeben.")
log.exception(msg)
control_command_log.info(msg)
self.step = 0
for s in [0, 0.25, 0.5, 0.75, 1.0]:
if self.step <= s:
self.step = s
break
self.step = self.get_step()
if changed:
Pub().pub(f"openWB/set/io/action/{self.config.id}/timestamp", create_timestamp())
control_command_log.info(f"EEBus-Steuerung: LPP-Wert {self.lpp_value} / "
f"max. PV-Leistung {max_output_inverter} = {self.step}")
control_command_log.info(f"EZA-Begrenzung mit Wert {self.step*100}% aktiviert.")
control_command_log.info(
f"EEBus-Steuerung: EZA-Begrenzung mit LPP-Wert {self.lpp_value}W aktiviert.")
for device in self.config.configuration.devices:
control_command_log.info(
f"Erzeugungsanlage {get_component_name_by_id(device['id'])} "
f"auf {self.step*100}% begrenzt."
f"auf {self.lpp_value}W begrenzt. Gestufte Ansteuerung: "
f"{self.step*100:.0f}% der maximalen Ausgangsleistung."
Comment on lines 94 to +98
)
else:
self.step = 1
if changed:
Pub().pub(f"openWB/set/io/action/{self.config.id}/timestamp", None)
control_command_log.info("EZA-Begrenzung aufgehoben.")
Expand All @@ -95,28 +109,17 @@ def control_stepwise(self) -> Tuple[Optional[float], LoadmanagementLimit]:
LimitingValue.CONTROLLABLE_CONSUMERS_ERROR.value.format(get_io_name_by_id(
self.config.configuration.io_device)),
LimitingValue.CONTROLLABLE_CONSUMERS_ERROR))
for pattern in self.config.configuration.input_pattern:
for digital_input, value in pattern["matrix"].items():
if data.data.io_states[f"io_states{self.config.configuration.io_device}"
].data.get.digital_input[digital_input] != value:
break

if self.lpp_active:
if self.step != 1:
limit = LoadmanagementLimit(
LimitingValue.CONTROL_STEPWISE.value.format(self.step*100),
LimitingValue.CONTROL_STEPWISE)
else:
if pattern["value"] is None:
return 0, LoadmanagementLimit(LimitingValue.MISSING_CONFIGURATION.value,
LimitingValue.MISSING_CONFIGURATION)
# Alle digitalen Eingänge entsprechen dem Pattern
elif pattern["value"] != 1:
limit = LoadmanagementLimit(
LimitingValue.CONTROL_STEPWISE.value.format(pattern["value"]*100),
LimitingValue.CONTROL_STEPWISE)
else:
limit = LoadmanagementLimit("Keine Leistungsbegrenzung aktiv.", None)
return pattern["value"], limit
limit = LoadmanagementLimit("Keine Leistungsbegrenzung aktiv.", None)
else:
# Zustand entspricht keinem Pattern
return 0, LoadmanagementLimit(
LimitingValue.CONTROL_STEPWISE.value.format(0),
LimitingValue.CONTROL_STEPWISE)
limit = LoadmanagementLimit("Keine Leistungsbegrenzung aktiv.", None)
return self.step, limit


def create_action(config: StepwiseControlSetup, parent_device_type: str):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from unittest.mock import Mock

import pytest

from control import data
from modules.io_actions.generator_systems.stepwise_control.api_eebus import StepwiseControlEebus
from modules.io_actions.generator_systems.stepwise_control.config import StepwiseControlConfig, StepwiseControlSetup


@pytest.fixture()
def mock_data() -> None:
data.data_init(Mock())


def _build_action(lpp_value: float, max_ac_out: float) -> StepwiseControlEebus:
config = StepwiseControlSetup(configuration=StepwiseControlConfig(
io_device=0,
devices=[{"type": "inverter", "id": 1}],
))
action = StepwiseControlEebus(config)
action.lpp_value = lpp_value
data.data.pv_data["pv1"] = Mock(data=Mock(config=Mock(max_ac_out=max_ac_out)))
return action


@pytest.mark.parametrize(
"lpp_value,max_ac_out,expected",
[
pytest.param(0, 10000, 0, id="0W -> 0%"),
pytest.param(1000, 10000, 0.3, id="10% -> 30%"),
pytest.param(3000, 10000, 0.3, id="30% -> 30%"),
pytest.param(4500, 10000, 0.6, id="45% -> 60%"),
pytest.param(6000, 10000, 0.6, id="60% -> 60%"),
pytest.param(9000, 10000, 1.0, id="90% -> 100%"),
],
)
def test_get_step(mock_data, lpp_value, max_ac_out, expected):
action = _build_action(lpp_value=lpp_value, max_ac_out=max_ac_out)

assert action.get_step() == expected


def test_get_step_handles_zero_division(mock_data):
action = _build_action(lpp_value=5000, max_ac_out=0)

assert action.get_step() == 0
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class StepwiseControlConfig:
devices: List[Dict] = field(default_factory=empty_list_factory)
# [{"type": "inverter", "id": 1},...]
passthrough_enabled: bool = False
io_output_device: Optional[int] = None
output_pattern: List[Dict] = field(default_factory=empty_io_pattern_stepwise_factory)


Expand Down