io action: error of io device use remaining energy, show message#3080
io action: error of io device use remaining energy, show message#3080LKuemmel merged 3 commits intoopenWB:masterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Diese PR erweitert die IO-Actions um ein Failsafe-Verhalten bei IO-Gerätefehlern und liefert zusätzlich strukturierte Limit-Informationen (Message + LimitingValue), damit im System/Frontend aussagekräftige Meldungen angezeigt werden können (z.B. bei Dimmung/RSE/Stufensteuerung).
Changes:
- IO-Actions geben nun i.d.R. ein Tuple
(value, LoadmanagementLimit)zurück und berücksichtigen den IO-Fehlerzustand als Failsafe-Trigger. - Loadmanagement und PV-All verwenden die neuen Limit-Metadaten für Anzeige/Weiterverarbeitung.
- Gemeinsame Helper-Funktion zur Erkennung des IO-Fault-States eingeführt und LimitingValue-Enum erweitert.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 17 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/modules/io_actions/generator_systems/stepwise_control/api_io.py | Stepwise-Control liefert Limit-Metadaten + Failsafe bei IO-Fehler. |
| packages/modules/io_actions/generator_systems/stepwise_control/api_eebus.py | Stepwise-Control (EEBus) um Limit-Metadaten/Failsafe erweitert. |
| packages/modules/io_actions/controllable_consumers/ripple_control_receiver/api.py | RSE liefert Limit-Metadaten/Failsafe und erweitert Logging/Timestamp-Handling. |
| packages/modules/io_actions/controllable_consumers/dimming_direct_control/api.py | Direktsteuerungs-Dimmung liefert Limit-Metadaten/Failsafe. |
| packages/modules/io_actions/controllable_consumers/dimming/api_io.py | IO-Dimmung liefert Limit-Metadaten/Failsafe und Logging-Erweiterungen. |
| packages/modules/io_actions/controllable_consumers/dimming/api_eebus.py | EEBus-Dimmung liefert Limit-Metadaten/Failsafe und Logging-Erweiterungen. |
| packages/modules/io_actions/common.py | Neuer Helper check_fault_state_io_device() für wiederverwendbare Fault-Erkennung. |
| packages/control/pv_all.py | PV-Fault-String basiert nun auf Stepwise-Control-Limit-Metadaten. |
| packages/control/loadmanagement.py | Loadmanagement nutzt Tuple-Returns der IO-Actions und propagiert Limits. |
| packages/control/limiting_value.py | Neue/angepasste LimitingValue-Texte + CONTROL_STEPWISE + Missing-Config-Text. |
| packages/control/io_device.py | IoActions-API auf Tuple-Return umgestellt (Value + LoadmanagementLimit). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def dimming_via_direct_control(self) -> Tuple[Optional[float], LoadmanagementLimit]: | ||
| if check_fault_state_io_device(self.config.configuration.io_device): | ||
| return (4200, LoadmanagementLimit( | ||
| LimitingValue.CONTROLLABLE_CONSUMERS_ERROR.value.format(get_io_name_by_id( | ||
| self.config.configuration.io_device)), | ||
| LimitingValue.CONTROLLABLE_CONSUMERS_ERROR)) | ||
| elif data.data.io_states[f"io_states{self.config.configuration.io_device}"].data.get.digital_input[ | ||
| self.dimming_input] == self.dimming_value: | ||
| return 4200 | ||
| return (4200, LoadmanagementLimit(LimitingValue.DIMMING_VIA_DIRECT_CONTROL.value, | ||
| LimitingValue.DIMMING_VIA_DIRECT_CONTROL)) |
There was a problem hiding this comment.
Die Methode dimming_via_direct_control() liefert jetzt ein (value, LoadmanagementLimit)-Tuple. Es gibt jedoch direkte Call-Sites, die weiterhin None/float erwarten (z.B. packages/control/process.py prüft action.dimming_via_direct_control() is None). Bitte diese Call-Sites auf das neue Return-Format umstellen (z.B. erstes Element auspacken).
| if check_fault_state_io_device(self.config.configuration.io_device): | ||
| log_active_ripple_control_receiver() | ||
| for pattern in self.config.configuration.input_pattern: | ||
| for digital_input, value in pattern["matrix"].items(): |
There was a problem hiding this comment.
Der Aufruf log_active_ripple_control_receiver() passiert hier, bevor in der folgenden Schleife überhaupt ein pattern bestimmt wurde. Dadurch wird im Fehlerfall die Logging-Funktion mit nicht initialisierten Kontextdaten ausgeführt. Entweder den ersten Aufruf entfernen oder erst nach Auswahl eines passenden Patterns loggen.
| # Alle digitalen Eingänge entsprechen dem Pattern | ||
| return pattern["value"] if pattern["value"] is not None else 0 | ||
| if pattern["value"] is None: | ||
| return 0, LoadmanagementLimit(LimitingValue.MISSING_CONFIFGURATION, |
There was a problem hiding this comment.
LoadmanagementLimit.message ist als Optional[str] definiert, hier wird aber LimitingValue.MISSING_CONFIFGURATION (Enum) übergeben. Das führt später zu ungewollten Strings wie LimitingValue.MISSING_CONFIFGURATION in der UI. Verwende für die Message LimitingValue.MISSING_CONFIFGURATION.value und als limiting_value nur das Enum (nicht .value).
| return 0, LoadmanagementLimit(LimitingValue.MISSING_CONFIFGURATION, | |
| return 0, LoadmanagementLimit(LimitingValue.MISSING_CONFIFGURATION.value, |
| break | ||
| else: | ||
| if pattern["value"] is None: | ||
| return 0, LoadmanagementLimit(LimitingValue.MISSING_CONFIFGURATION, |
There was a problem hiding this comment.
LoadmanagementLimit.message ist Optional[str], hier wird aber LimitingValue.MISSING_CONFIFGURATION (Enum) übergeben. Dadurch landet später der Enum-Name statt der eigentlichen Meldung im fault_str. Bitte .value für die Message verwenden und limiting_value=LimitingValue.MISSING_CONFIFGURATION setzen.
| return 0, LoadmanagementLimit(LimitingValue.MISSING_CONFIFGURATION, | |
| return 0, LoadmanagementLimit(LimitingValue.MISSING_CONFIFGURATION.value, |
|
|
||
|
|
||
| def check_fault_state_io_device(io_device: int) -> bool: | ||
| return data.data.io_states[f"io_states{io_device}"].data.get.fault_state == 2 |
There was a problem hiding this comment.
In check_fault_state_io_device() wird der Fehlerzustand mit der Magic Number 2 geprüft. Für bessere Lesbarkeit/Wartbarkeit könnte hier FaultStateLevel.ERROR (siehe modules/common/fault_state_level.py) verwendet werden.
| def check_fault_state_io_device(io_device: int) -> bool: | |
| return data.data.io_states[f"io_states{io_device}"].data.get.fault_state == 2 | |
| from modules.common.fault_state_level import FaultStateLevel | |
| def check_fault_state_io_device(io_device: int) -> bool: | |
| return ( | |
| data.data.io_states[f"io_states{io_device}"].data.get.fault_state | |
| == FaultStateLevel.ERROR | |
| ) |
packages/control/loadmanagement.py
Outdated
| @@ -177,9 +175,4 @@ def _limit_by_ripple_control_receiver(self, | |||
| available_currents = [min(max_current*value - cp.data.set.target_current, c) | |||
There was a problem hiding this comment.
Hier können bei value=0 (z.B. Fehler-/Failsafe-Fall) negative Ströme entstehen, weil max_current*value - cp.data.set.target_current negativ wird und per min() übernommen werden kann. Bitte den berechneten Strom auf mindestens 0A clampen, bevor er in available_currents übernommen wird.
| available_currents = [min(max_current*value - cp.data.set.target_current, c) | |
| available_currents = [max(min(max_current*value - cp.data.set.target_current, c), 0) |
| value, limit = data.data.io_actions.dimming_via_direct_control({"type": "cp", "id": cp.num}) | ||
| if value is not None: |
There was a problem hiding this comment.
Die geänderte IO-Action-Integration (Tuple-Return mit LoadmanagementLimit) in _limit_by_dimming_via_direct_control, _limit_by_dimming und _limit_by_ripple_control_receiver ist aktuell nicht durch Unit-Tests abgedeckt (in loadmanagement_test.py werden nur _limit_by_power und _limit_by_current getestet). Bitte Tests ergänzen, die die neue Limit-Auswahl und die Fehler-/Failsafe-Pfade verifizieren.
| limit = LoadmanagementLimit( | ||
| LimitingValue.CONTROL_STEPWISE.value.format(pattern["value"]*100), |
There was a problem hiding this comment.
In der Meldung wird pattern["value"]*100 direkt formatiert. Da pattern["value"] i.d.R. ein Float ist (z.B. 0.5), ergibt das user-facing Werte wie 50.0%. Bitte vor dem Formatieren sinnvoll runden/als int ausgeben, um konsistente Prozentangaben zu erhalten.
| limit = LoadmanagementLimit( | |
| LimitingValue.CONTROL_STEPWISE.value.format(pattern["value"]*100), | |
| percentage = int(round(pattern["value"] * 100)) | |
| limit = LoadmanagementLimit( | |
| LimitingValue.CONTROL_STEPWISE.value.format(percentage), |
| limit = LoadmanagementLimit( | ||
| LimitingValue.CONTROL_STEPWISE.value.format(pattern["value"]*100), |
There was a problem hiding this comment.
Die Prozentangabe nutzt pattern["value"]*100 ohne Rundung/Konvertierung. Das führt bei Float-Werten zu Ausgaben wie 50.0%. Bitte die Prozentwerte für die Nutzeranzeige runden (z.B. int/round).
| limit = LoadmanagementLimit( | |
| LimitingValue.CONTROL_STEPWISE.value.format(pattern["value"]*100), | |
| percent_value = round(pattern["value"] * 100) | |
| limit = LoadmanagementLimit( | |
| LimitingValue.CONTROL_STEPWISE.value.format(int(percent_value)), |
| with ModifyLoglevelContext(control_command_log, logging.DEBUG): | ||
| if check_fault_state_io_device(self.config.configuration.io_device): | ||
| log_active_ripple_control_receiver() | ||
| for pattern in self.config.configuration.input_pattern: | ||
| for digital_input, value in pattern["matrix"].items(): |
There was a problem hiding this comment.
In setup() werden die Input-Patterns nur noch ausgewertet, wenn das IO-Gerät im Fehlerzustand ist. Im Normalfall (fault_state != 2) wird dadurch nie aktiviert bzw. kein Timestamp gesetzt, obwohl die Pattern evtl. aktiv sind. Bitte die Pattern-Auswertung wieder auch im Normalfall durchführen (Fault-State nur als zusätzlicher Aktivierungsgrund behandeln).
https://forum.openwb.de/viewtopic.php?p=138295#p138295
Bei der Dimmung wird im Failsafe-Mode die Leistung zur Verfügung gestellt, die auch bei Aktivierung durch den Netzbetreiber zur Verfügung stehen würde.
Der Nutzer bekommt eine Meldung am Ladepunkt, dass seine Ladung gestoppt/reduziert wurde, weil das IO-Gerät im Fehlerzustand ist.
Dimmen per EMS
Dimmen per Direktsteuerung
RSE
Erzeuger