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
6 changes: 5 additions & 1 deletion docs/IO-Geräte & -Aktionen.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ Die AddOn-Platine stellt 7 Eingänge und 3 Ausgänge zur Verfügung. WICHTIG: In

### Steuerbare Verbrauchseinrichtungen: Dimmen per EMS, Dimmung per Direkt-Steuerung, RSE

Ausführliche Informationen findest Du im gesonderten Wiki-Beitrag [Steuerbare Verbrauchseinrichtungen](https://github.com/openWB/core/wiki/Steuerbare-Verbrauchseinrichtungen-nach-§14a)
Ausführliche Informationen findest Du im gesonderten Wiki-Beitrag [Steuerbare Einrichtungen nach § 14a EnGW und § 9 EEG](https://github.com/openWB/core/wiki/Steuerbare-Verbrauchseinrichtungen-nach-§14a)

### Steuerbare Erzeugungseinrichtungen: Stufenweise Steuerung
Bitte beachten: Die openWB steuert keinen Wechselrichter an. Sie zeigt lediglich den aktuellen Zustand der Beschränkung an.
Ausführliche Informationen findest Du im gesonderten Wiki-Beitrag [Steuerbare Einrichtungen nach § 14a EnGW und § 9 EEG](https://github.com/openWB/core/wiki/Steuerbare-Verbrauchseinrichtungen-nach-§14a)

## Manuelles Setzen der Ausgänge
Die Ausgänge aller IO-Geräte können per MQTT gesetzt werden. Die Topics findet Ihr in den Einstellungen des jeweiligen Geräts als Copy-to-Clipboard-Link. Das manuelle Setzen des Ausgangs überschreibt den Wert, den zB die openWB bei einer IO-Aktion gesetzt hat.
13 changes: 13 additions & 0 deletions docs/Steuerbare Verbrauchseinrichtungen nach §14a.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
## Stuerbare Verbrauchseinrichtungen (SteuVE) nach § 14a EnGW
Der Gesetzgeber sieht verschiedene Möglichkeiten für steuerbare Verbrauchseinrichtungen vor. Für jede steuerbare Verbrauchseinrichtung kann eine andere Option angemeldet werden. Bei der Konfiguration muss deshalb auch immer der/die Ladepunkte angegeben werden, für die die IO-Aktion angewendet werden soll.

### Dimmen per EMS
Expand All @@ -13,3 +14,15 @@ Pro steuerbarer Verbrauchseinrichtung muss eine IO-Aktion konfiguriert werden un
### Rundsteuer-Empfänger-Kontakt (RSE)

Für den RSE-Kontakt kann ein Muster aus verschiedenen Eingängen und ein Prozentwert, auf den die Anschlussleistung begrenzt wird, angegeben werden.

## Steuerbare Erzeugungsanlagen (EZA) nach § 9 EEG

Bitte beachten: Die openWB steuert keinen Wechselrichter an. Sie zeigt lediglich den aktuellen Zustand der Beschränkung an.

Die Einspeiseleistung des Wechselrichters wird über drei Signalkontakte der FNN-Steuerbox geregelt. Die openWB übernimmt dabei keine direkte Steuerung des Wechselrichters, sondern visualisiert lediglich den aktuellen Steuerzustand. Das Signalkabel der FNN-Steuerbox muss daher beispielsweise über eine Doppelklemme mit dem I/O-Modul der openWB verbunden und anschließend zum Wechselrichter weitergeführt (durchgeschliffen) werden.
Die Signalkontakte bilden folgende Zustände ab:
S1 -> 60% der EZA
S2 -> 30% der EZA
W3 -> 0% der EZA
alle Kontakte offen -> 100% der EZA
Sollten mehrere Kontakte geschlossen sein, so wird die geringste Leistungsstufe ausgewählt (z. B. S2 und W3 -> 0%).
18 changes: 17 additions & 1 deletion packages/control/io_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from modules.io_actions.controllable_consumers.dimming.api import Dimming
from modules.io_actions.controllable_consumers.dimming_direct_control.api import DimmingDirectControl
from modules.io_actions.controllable_consumers.ripple_control_receiver.api import RippleControlReceiver
from modules.io_actions.production_plants.stepwise_control.api import StepwiseControl


@dataclass
Expand All @@ -15,6 +16,10 @@ class Get:
analog_output: Dict[int, float] = None
digital_input: Dict[int, bool] = None
digital_output: Dict[int, bool] = None
analog_input_prev: Dict[int, float] = None
analog_output_prev: Dict[int, float] = None
digital_input_prev: Dict[int, bool] = None
digital_output_prev: Dict[int, bool] = None
fault_str: str = NO_ERROR
fault_state: int = 0

Expand All @@ -26,7 +31,9 @@ def get_factory():
@dataclass
class Set:
analog_output: Dict[int, float] = None
analog_output_prev: Dict[int, float] = None
digital_output: Dict[int, bool] = None
digital_output_prev: Dict[int, bool] = None


def set_factory():
Expand All @@ -47,7 +54,7 @@ def __init__(self, num: Union[int, str]):

class IoActions:
def __init__(self):
self.actions: Dict[int, Union[Dimming, DimmingDirectControl, RippleControlReceiver]] = {}
self.actions: Dict[int, Union[Dimming, DimmingDirectControl, RippleControlReceiver, StepwiseControl]] = {}

def setup(self):
for action in self.actions.values():
Expand Down Expand Up @@ -93,3 +100,12 @@ def ripple_control_receiver(self, device: Dict) -> float:
return action.ripple_control_receiver()
else:
return 1

def stepwise_control(self, device_id: int) -> Optional[str]:
for action in self.actions.values():
if isinstance(action, StepwiseControl):
if device_id == action.config.configuration.pv_id:
self._check_fault_state_io_device(action.config.configuration.io_device)
return action.control_stepwise()
else:
return None
4 changes: 4 additions & 0 deletions packages/control/pv_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ def calc_power_for_all_components(self) -> None:
else:
if fault_state < module_data.get.fault_state:
fault_state = module_data.get.fault_state
msg = data.data.io_actions.stepwise_control(data.data.pv_data[module].num)
if msg is not None and data.data.pv_data[module].data.get.fault_state == 0:
data.data.pv_data[module].data.get.fault_str = msg
Pub().pub(f"openWB/set/pv/{data.data.pv_data[module].num}/get/fault_str", msg)
except Exception:
log.exception("Fehler im allgemeinen PV-Modul für "+str(module))
if fault_state == 0:
Expand Down
13 changes: 13 additions & 0 deletions packages/modules/common/store/_io.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from control import data
from modules.common.component_state import IoState
from modules.common.fault_state import FaultState
from modules.common.store import ValueStore
Expand All @@ -16,14 +17,26 @@ def set(self, state: IoState) -> None:
def update(self):
try:
if self.state.digital_input:
pub_to_broker(f"openWB/set/io/states/{self.num}/get/digital_input_prev",
data.data.io_states[f"io_states{self.num}"].data.get.digital_input)
pub_to_broker(f"openWB/set/io/states/{self.num}/get/digital_input", self.state.digital_input)
if self.state.analog_input:
pub_to_broker(f"openWB/set/io/states/{self.num}/get/analog_input_prev",
data.data.io_states[f"io_states{self.num}"].data.get.analog_input)
pub_to_broker(f"openWB/set/io/states/{self.num}/get/analog_input", self.state.analog_input)
if self.state.digital_output:
pub_to_broker(f"openWB/set/io/states/{self.num}/get/digital_output_prev",
data.data.io_states[f"io_states{self.num}"].data.get.digital_output)
pub_to_broker(f"openWB/set/io/states/{self.num}/get/digital_output", self.state.digital_output)
pub_to_broker(f"openWB/set/io/states/{self.num}/set/digital_output_prev",
data.data.io_states[f"io_states{self.num}"].data.set.digital_output)
pub_to_broker(f"openWB/set/io/states/{self.num}/set/digital_output", self.state.digital_output)
if self.state.analog_output:
pub_to_broker(f"openWB/set/io/states/{self.num}/get/analog_output_prev",
data.data.io_states[f"io_states{self.num}"].data.get.analog_output)
pub_to_broker(f"openWB/set/io/states/{self.num}/get/analog_output", self.state.analog_output)
pub_to_broker(f"openWB/set/io/states/{self.num}/set/analog_output_prev",
data.data.io_states[f"io_states{self.num}"].data.set.analog_output)
pub_to_broker(f"openWB/set/io/states/{self.num}/set/analog_output", self.state.analog_output)
except Exception as e:
raise FaultState.from_exception(e)
Expand Down
2 changes: 2 additions & 0 deletions packages/modules/io_actions/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

class ActionGroup(Enum):
CONTROLLABLE_CONSUMERS = "controllable_consumers"
PRODUCTION_PLANTS = "production_plants"


READABLE_GROUP_NAME = {
ActionGroup.CONTROLLABLE_CONSUMERS: "Steuerbare Verbrauchseinrichtungen (§14a)",
ActionGroup.PRODUCTION_PLANTS: "Erzeugungsanlagen (§9)",
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import logging
from control import data
from typing import Optional
from helpermodules.logger import ModifyLoglevelContext
from modules.common.abstract_device import DeviceDescriptor
from modules.common.abstract_io import AbstractIoAction
from modules.common.utils.component_parser import get_component_name_by_id
from modules.io_actions.production_plants.stepwise_control.config import StepwiseControlSetup

control_command_log = logging.getLogger("steuve_control_command")


class StepwiseControl(AbstractIoAction):
def __init__(self, config: StepwiseControlSetup):
self.config = config
control_command_log.info(f"Stufenweise Steuerung einer EZA: Eingang {self.config.configuration.s1} für S1, "
f"Eingang {self.config.configuration.s2} für S2, und Eingang "
f"{self.config.configuration.w3} für W3 wird überwacht. Die Beschränkung musss in "
"der EZA vorgenommen werden.")
super().__init__()

def setup(self) -> None:
pass

def control_stepwise(self) -> Optional[str]:
text = (f"Die Einspeiseleistung von {get_component_name_by_id(self.config.configuration.pv_id)} ist auf "
"{} % beschränkt. Die Beschränkung musss in der EZA vorgenommen werden.")
msg = None
digital_input = data.data.io_states[f"io_states{self.config.configuration.io_device}"].data.get.digital_input
digital_input_prev = data.data.io_states[
f"io_states{self.config.configuration.io_device}"].data.get.digital_input_prev

active_inputs = [
digital_input[self.config.configuration.s1],
digital_input[self.config.configuration.s2],
digital_input[self.config.configuration.w3]
]
num_active = sum(1 for v in active_inputs if v)

if num_active > 1:
error_msg = (f"Fehler: Mehr als ein Eingang ist aktiv für die stufenweise Steuerung der EZA! "
f"S1: {digital_input[self.config.configuration.s1]}, "
f"S2: {digital_input[self.config.configuration.s2]}, "
f"W3: {digital_input[self.config.configuration.w3]}")
with ModifyLoglevelContext(control_command_log, logging.ERROR):
control_command_log.error(error_msg)
raise ValueError(error_msg)

if digital_input[self.config.configuration.s1]:
msg = text.format(60)
elif digital_input[self.config.configuration.s2]:
msg = text.format(30)
elif digital_input[self.config.configuration.w3]:
msg = text.format(0)
else:
# Keine Beschränkung soll nicht dauerhaft im WR angezeigt werden.
msg = (f"Die Einspeiseleistung von {get_component_name_by_id(self.config.configuration.pv_id)} ist "
"nicht beschränkt. Die Beschränkung musss in der EZA vorgenommen werden.")

if not (digital_input[self.config.configuration.s1] == digital_input_prev[self.config.configuration.s1] and
digital_input[self.config.configuration.s2] == digital_input_prev[self.config.configuration.s2] and
digital_input[self.config.configuration.w3] == digital_input_prev[self.config.configuration.w3]):
# Wenn sich was geändet hat, loggen
with ModifyLoglevelContext(control_command_log, logging.DEBUG):
control_command_log.info(msg)
return msg


def create_action(config: StepwiseControlSetup):
return StepwiseControl(config=config)


device_descriptor = DeviceDescriptor(configuration_factory=StepwiseControlSetup)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from dataclasses import dataclass
from typing import Optional
from modules.io_actions.groups import ActionGroup


@dataclass
class StepwiseControlConfig:
io_device: Optional[int] = None
s1: str = None
s2: str = None
w3: str = None
pv_id: int = None


class StepwiseControlSetup:
def __init__(self,
name: str = "Stufenweise Steuerung einer EZA",
type: str = "stepwise_control",
id: int = 0,
configuration: StepwiseControlConfig = None):
self.name = name
self.type = type
self.id = id
self.configuration = configuration or StepwiseControlConfig()
self.group = ActionGroup.PRODUCTION_PLANTS.value