Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -35,25 +35,30 @@ def __init__(self, config: DimmingSetup):
super().__init__()

def setup(self) -> None:
lpc_value = data.data.io_states[f"io_states{self.config.configuration.io_device}"
].data.get.analog_input[AnalogInputMapping.LPC_VALUE.name]
surplus = data.data.counter_data[data.data.counter_all_data.get_evu_counter_str()].calc_raw_surplus()
if surplus > 0:
self.import_power_left = lpc_value + surplus

if check_fault_state_io_device(self.config.configuration.io_device):
self.import_power_left = surplus
else:
self.import_power_left = lpc_value
lpc_value = data.data.io_states[f"io_states{self.config.configuration.io_device}"
].data.get.analog_input[AnalogInputMapping.LPC_VALUE.name]
if surplus > 0:
self.import_power_left = lpc_value + surplus
else:
self.import_power_left = lpc_value
self.import_power_left -= self.config.configuration.fixed_import_power
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the IO device is in fault state you set self.import_power_left = surplus and then subtract fixed_import_power, which can make import_power_left <= 0. In control/loadmanagement.py:_limit_by_dimming the dimming limit is only applied when dimming_power_left is truthy (if dimming_power_left:), so non-positive values will disable limiting entirely. To keep failsafe behavior, clamp the computed power to at least 0 (or return a non-None sentinel and adjust the caller) so the limiter still enforces a stop/reduction.

Suggested change
self.import_power_left -= self.config.configuration.fixed_import_power
self.import_power_left = max(0, self.import_power_left - self.config.configuration.fixed_import_power)

Copilot uses AI. Check for mistakes.
log.debug(f"Dimmen: {self.import_power_left}W inkl. Überschuss")

with ModifyLoglevelContext(control_command_log, logging.DEBUG):
if self.dimming_active() or check_fault_state_io_device(self.config.configuration.io_device):
if check_fault_state_io_device(self.config.configuration.io_device) or self.dimming_active():
if self.timestamp is None:
Pub().pub(f"openWB/set/io/action/{self.config.id}/timestamp", create_timestamp())
if check_fault_state_io_device(self.config.configuration.io_device):
control_command_log.info(
"Fehler des IO-Geräts: Dimmen aktiviert für Failsafe-Modus.")
control_command_log.info(f"Dimmen aktiviert. Übermittelter LPC-Wert: {lpc_value/1000}kWh. "
"Leistungswerte vor Ausführung des Steuerbefehls:")
else:
control_command_log.info(f"Dimmen aktiviert. Übermittelter LPC-Wert: {lpc_value/1000}kWh. "
"Leistungswerte vor Ausführung des Steuerbefehls:")

evu_counter = data.data.counter_data[data.data.counter_all_data.get_evu_counter_str()]
msg = f"EVU-Zähler: {evu_counter.data.get.powers}W, {evu_counter.data.get.power}W"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def setup(self) -> None:
log.debug(f"Dimmen: {self.import_power_left}W inkl. Überschuss")

with ModifyLoglevelContext(control_command_log, logging.DEBUG):
if self.dimming_active() or check_fault_state_io_device(self.config.configuration.io_device):
if check_fault_state_io_device(self.config.configuration.io_device) or self.dimming_active():
if self.timestamp is None:
Pub().pub(f"openWB/set/io/action/{self.config.id}/timestamp", create_timestamp())
if check_fault_state_io_device(self.config.configuration.io_device):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,49 +44,50 @@ def __init__(self, config: StepwiseControlSetup):

def setup(self) -> None:
with ModifyLoglevelContext(control_command_log, logging.DEBUG):
self.lpp_value = data.data.io_states[f"io_states{self.config.configuration.io_device}"
].data.get.analog_input[AnalogInputMapping.LPP_VALUE.name]
lpp_value_prev = data.data.io_states[f"io_states{self.config.configuration.io_device}"
].data.get.analog_input_prev[AnalogInputMapping.LPP_VALUE.name]
self.lpp_active = data.data.io_states[f"io_states{self.config.configuration.io_device}"
].data.get.digital_input[DigitalInputMapping.LPP_ACTIVE.name]
lpp_active_prev = data.data.io_states[f"io_states{self.config.configuration.io_device}"
].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
if check_fault_state_io_device(self.config.configuration.io_device):
control_command_log.info("Fehler des IO-Geräts: EZA-Begrenzung kann nicht erfasst werden.")
else:
self.lpp_value = data.data.io_states[f"io_states{self.config.configuration.io_device}"
].data.get.analog_input[AnalogInputMapping.LPP_VALUE.name]
lpp_value_prev = data.data.io_states[f"io_states{self.config.configuration.io_device}"
].data.get.analog_input_prev[AnalogInputMapping.LPP_VALUE.name]
self.lpp_active = data.data.io_states[f"io_states{self.config.configuration.io_device}"
].data.get.digital_input[DigitalInputMapping.LPP_ACTIVE.name]
lpp_active_prev = data.data.io_states[f"io_states{self.config.configuration.io_device}"
].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
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
Comment on lines +61 to +62
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In max_output_inverter calculation you iterate over all self.config.configuration.devices and unconditionally access pv_data[f"pv{inverter['id']}"]. devices can also contain non-inverter entries (e.g. IO outputs), which would make this lookup invalid and can raise a KeyError or sum unrelated IDs. Filter the list to device["type"] == "inverter" (and only then access pv_data).

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

Copilot uses AI. Check for mistakes.

if self.lpp_active or check_fault_state_io_device(self.config.configuration.io_device):
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
if check_fault_state_io_device(self.config.configuration.io_device):
control_command_log.info("Fehler des IO-Geräts: EZA-Begrenzung kann nicht erfasst werden.")
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.")
for device in self.config.configuration.devices:
control_command_log.info(
f"Erzeugungsanlage {get_component_name_by_id(device)} "
f"auf {self.step*100}% begrenzt."
)
else:
if changed:
Pub().pub(f"openWB/set/io/action/{self.config.id}/timestamp", None)
control_command_log.info("EZA-Begrenzung aufgehoben.")
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
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.")
for device in self.config.configuration.devices:
control_command_log.info(
f"Erzeugungsanlage {get_component_name_by_id(device)} "
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_component_name_by_id expects an int component id, but here a full device dict is passed. This will cause get_component_name_by_id to raise a ValueError when this log line runs (e.g. when LPP becomes active and changed is true). Pass device['id'] (and likely only log for device['type'] == 'inverter').

Suggested change
control_command_log.info(
f"Erzeugungsanlage {get_component_name_by_id(device)} "
if device.get("type") != "inverter":
continue
control_command_log.info(
f"Erzeugungsanlage {get_component_name_by_id(device['id'])} "

Copilot uses AI. Check for mistakes.
f"auf {self.step*100}% begrenzt."
)
else:
if changed:
Pub().pub(f"openWB/set/io/action/{self.config.id}/timestamp", None)
control_command_log.info("EZA-Begrenzung aufgehoben.")

def control_stepwise(self) -> Tuple[Optional[float], LoadmanagementLimit]:
if check_fault_state_io_device(self.config.configuration.io_device):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,41 +47,43 @@ def __init__(self, config: StepwiseControlSetup):

def setup(self) -> None:
with ModifyLoglevelContext(control_command_log, logging.DEBUG):
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
changed = len([
input_name for input_name in self.__unique_inputs
if digital_input[input_name] != digital_input_prev[input_name]
]) > 0

if check_fault_state_io_device(self.config.configuration.io_device):
control_command_log.info("Fehler des IO-Geräts: EZA-Begrenzung kann nicht erfasst werden.")
for pattern in self.config.configuration.input_pattern:
for action_input, value in pattern["matrix"].items():
if digital_input[action_input] != value:
break
else:
# Alle digitalen Eingänge entsprechen dem Pattern
if pattern["value"] != 1:
if changed:
Pub().pub(f"openWB/set/io/action/{self.config.id}/timestamp", create_timestamp())
control_command_log.info(f"EZA-Begrenzung mit Wert {int(pattern['value']*100)}% aktiviert.")
for device in self.config.configuration.devices:
if device["type"] == "inverter":
control_command_log.info(
f"Erzeugungsanlage {get_component_name_by_id(device['id'])} "
f"auf {int(pattern['value']*100)}% begrenzt."
)
break
else:
if changed:
Pub().pub(f"openWB/set/io/action/{self.config.id}/timestamp", None)
control_command_log.info("EZA-Begrenzung aufgehoben.")
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
changed = len([
input_name for input_name in self.__unique_inputs
if digital_input[input_name] != digital_input_prev[input_name]
]) > 0

for pattern in self.config.configuration.input_pattern:
for action_input, value in pattern["matrix"].items():
if digital_input[action_input] != value:
break
else:
# Alle digitalen Eingänge entsprechen dem Pattern
if pattern["value"] != 1:
if changed:
Pub().pub(f"openWB/set/io/action/{self.config.id}/timestamp", create_timestamp())
control_command_log.info(
f"EZA-Begrenzung mit Wert {int(pattern['value']*100)}% aktiviert.")
for device in self.config.configuration.devices:
if device["type"] == "inverter":
control_command_log.info(
f"Erzeugungsanlage {get_component_name_by_id(device['id'])} "
f"auf {int(pattern['value']*100)}% begrenzt."
)
break
else:
if changed:
Pub().pub(f"openWB/set/io/action/{self.config.id}/timestamp", None)
control_command_log.info("EZA-Begrenzung aufgehoben.")

def control_stepwise(self) -> Tuple[Optional[float], LoadmanagementLimit]:
if check_fault_state_io_device(self.config.configuration.io_device):
Expand Down
Loading