Seite 1 von 2

SolarEdge Speichersteuerung

Verfasst: Mi Feb 26, 2025 12:34 am
von ChristophR
Ich lagere das Thema zur Speichersteuerung mal in einen eigenen Thread aus.
ChristophR hat geschrieben: Di Feb 18, 2025 7:41 am
Basti hat geschrieben: Di Feb 18, 2025 7:23 am Es gibt für solaredge einen pull requests für die Batterie Steuerung , der steht aber noch als Entwurf drin , könnt ihr gerne testen , bzw. Ich werde ihn testen wenn hier mal wieder die Sonne scheint am Wochenende unter der Woche habe ich leider keine Zeit zum testen
Der Pull request dazu ist hier zu finden:
https://github.com/openWB/core/pull/2108
Kannst Du erklären, was man hier machen soll:
Hinweis: Für die aktive Steuerung muss der Speicher auf Control Mode 4 (Remote) gesetzt werden (StorageConf_CtrlMode (0xE004)). Dies muss extern erfolgen und kann nicht von OpenWB automatisch übernommen werden.
Hallo Basti,

super dass Du Dich der Speichersteuerung für SolarEdge angenommen hast.
Ich habe mich jetzt mal ein bisschen damit beschäftigt.
Hierbei stellt sich mir die Frage, ob es evtl. Wechselwirkungen beim setzen des Storage Control Mode gibt?
Je nach Ausgangslage steht der Wert vermutlich meist auf:
1 - Maximaler Eigenverbrauch
Wenn der Installateur oder inzwischen auch der Betreiber mit der mySolarEdge App ein individuelles Profil hinterlegt hat steht es auf:
2- Time of Use

Der Modus 4 heißt in der SetApp Oberfläche "Time Of Use + Remote", ist also vermutlich eigentlich ein SubModus von "Time of Use".

Ich habe jetzt mal ein bisschen rumprobiert, was passiert, wenn etwas verändert wird:
1. Der Modus wird per Modbus auf 4 gesetzt.
2. Wenn das Profil geändert wird oder von Profil auf Max.Eigenverbrauch umgestellt wird (Egal ob durch Installateur oder Betreiber), wird der Wert wieder überschrieben und ändert sich also wieder auf 1 bzw. 2.

Damit der eingestellte Modus (Max.Eigenverbrauch oder die Profile) beim Verlassen der Speichersteuerung durch openWB wieder funktioniert, müsste ja auf jeden Fall der vorherige Wert zwischengespeichert werden, damit er danach wieder gesetzt werden kann?
Auch stellt sich mir die Frage, ob durch ein unvorhergesehenes Ereignis (z.B. reboot oder crash der openWB während der Speichersteuerung) evtl. ein inkonsistenter Zustand hinterlassen werden könnte und wie dieser wieder eingefangen wird.
Beim Programmieren kann ich leider gar nicht helfen, da bin ich eher Laie, bin nur interessierter Anwender.

Ich probiere gerade mal aus, ob sich der Modus ohne zutun von selbst wieder zurückstellt, z.B. durch regelmäßige Updates der SolarEdge Cloud.
Dafür habe ich das Entladelimit mal auf 4000W eingestellt und den Modus auf 4.
Bin gespannt...

Re: SolarEdge Speichersteuerung

Verfasst: Mi Feb 26, 2025 1:14 pm
von knotzchef
Hi,

ich würde hier auch mit aufspringen, da ich nun auch den 2. WR mit einem Speicher versehen werde.

-> auch dann den Kompromiss wenn man den Speicher eventuell über Solaredge ONE je nach Strompreis aus Netz Nachts nachladen könnte / will

Re: SolarEdge Speichersteuerung

Verfasst: Mi Feb 26, 2025 11:08 pm
von ChristophR
ChristophR hat geschrieben: Mi Feb 26, 2025 12:34 am
Basti hat geschrieben: Di Feb 18, 2025 7:23 am Es gibt für solaredge einen pull requests für die Batterie Steuerung , der steht aber noch als Entwurf drin , könnt ihr gerne testen , bzw. Ich werde ihn testen wenn hier mal wieder die Sonne scheint am Wochenende unter der Woche habe ich leider keine Zeit zum testen
Hallo Basti,

super dass Du Dich der Speichersteuerung für SolarEdge angenommen hast.
Ich habe mich jetzt mal ein bisschen damit beschäftigt.
Hierbei stellt sich mir die Frage, ob es evtl. Wechselwirkungen beim setzen des Storage Control Mode gibt?
Je nach Ausgangslage steht der Wert vermutlich meist auf:
1 - Maximaler Eigenverbrauch
Wenn der Installateur oder inzwischen auch der Betreiber mit der mySolarEdge App ein individuelles Profil hinterlegt hat steht es auf:
2- Time of Use

Der Modus 4 heißt in der SetApp Oberfläche "Time Of Use + Remote", ist also vermutlich eigentlich ein SubModus von "Time of Use".

Ich habe jetzt mal ein bisschen rumprobiert, was passiert, wenn etwas verändert wird:
1. Der Modus wird per Modbus auf 4 gesetzt.
2. Wenn das Profil geändert wird oder von Profil auf Max.Eigenverbrauch umgestellt wird (Egal ob durch Installateur oder Betreiber), wird der Wert wieder überschrieben und ändert sich also wieder auf 1 bzw. 2.

Damit der eingestellte Modus (Max.Eigenverbrauch oder die Profile) beim Verlassen der Speichersteuerung durch openWB wieder funktioniert, müsste ja auf jeden Fall der vorherige Wert zwischengespeichert werden, damit er danach wieder gesetzt werden kann?
Auch stellt sich mir die Frage, ob durch ein unvorhergesehenes Ereignis (z.B. reboot oder crash der openWB während der Speichersteuerung) evtl. ein inkonsistenter Zustand hinterlassen werden könnte und wie dieser wieder eingefangen wird.
Beim Programmieren kann ich leider gar nicht helfen, da bin ich eher Laie, bin nur interessierter Anwender.

Ich probiere gerade mal aus, ob sich der Modus ohne zutun von selbst wieder zurückstellt, z.B. durch regelmäßige Updates der SolarEdge Cloud.
Dafür habe ich das Entladelimit mal auf 4000W eingestellt und den Modus auf 4.
Bin gespannt...
24h später:
Der Storage Control Mode (0xE004) blieb auf 4 (Remote Control) stehen.
Das Remote Control Command Discharge Limit (0xE010) hat sich von 4000W wieder auf den Standard-Wert 5000W zurückgesetzt.

Generell gilt augenscheinlich aber folgendes:
Wenn man nur den Storage Control Mode (0x004) auf 4 (Remote Control) stellt, aber der Default Mode (0xE00A) und der Command Mode (0xE00D) auf dem Standardwert 0 stehen bleiben, dann wird der Speicher gar nicht mehr benutzt.

Man muss also diese auch umstellen (z.B. auf 7 - Maximaler Eigenverbrauch) und kann dann mit dem Discharge Limit die Speicherentladung begrenzen.
Der Timeout ist im Standard auf 3600 sec gesetzt, wenn also dann keine weitere Ansteuerung mehr erfolgt, fällt er auf den Default Mode.

Für den einfachsten Fall (Das System ist im Ausgangszustand auf "Maximaler Eigenverbrauch" und nicht auf "Time of Use" eingestellt, könnte der Workflow so funktionieren:
1. Auslesen des Storage Control Mode (0xE004)
2. Steht der auf 1 (Max.Eigenverbrauch), dann 0xE00A und 0xE00D auf 7 setzen (Max.Eigenverbrauch für Default- und Command Mode)
3. Setzen des Storage Control Mode (0xE004) auf 4 (Remote Control)
4. Nun kann das Discharge Limit (0xE010) gesetzt werden, um die Speicherentladung zu verhindern.
5. Wenn als Speicherleistung "None" übergeben wird, wird das Discharge Limit (0xE010) auf 5000W gesetzt.

Im nächsten Fall steht das System bereits auf Remote Control:
1. Auslesen des Storage Control Mode (0xE004)
2. Steht auf 4 (Remote Control). Nun zur Sicherheit nur den Command Mode (0xE00D) auf 7 setzen (Max. Eigenverbrauch), falls das System noch durch ein weiteres System gesteuert wird, lieber nicht den Default Mode ändern.
3. Nun kann, wie oben, das Discharge Limit (0xE010) gesetzt werden.
4. Bei "None" wieder Discharge Limit (0xE010) auf 5000W setzen.

So sollte der normale Ablauf der Speichersteuerung m.E. erstmal funktionieren, wäre das so umsetzbar?

Für den schwierigeren Fall, dass das System auf "Time of Use" steht, probiere ich noch aus, was passiert, wenn die Profile den Modus umschalten.
Ob dabei der Remote Control Mode jeweils abgeschaltet wird oder erhalten bleibt.
Dann können wir leichter entschieden, wie wir damit umgehen können.
Leider habe ich keine Möglichkeit gefunden, auszulesen, welcher Modus in dem Profil gerade aktiv ist, daher könnte das etwas schwieriger werden.

P.S: Die Referenz für die Modbus Register stammen aus dem vorherigen Thread:
https://forum.iobroker.net/assets/uploa ... erters.pdf

Re: SolarEdge Speichersteuerung

Verfasst: Do Feb 27, 2025 8:02 pm
von ChristophR
So, habe nun mit den Profilen im SolarEdge Monitoring-Portal getestet und es wird viel einfacher.
Wenn das System auf Max.Eigenverbrauch steht, ist das Register 0xE004 zwar auf 1, jedoch bleibt das System auch bei der Einstellung 2 bei Max.Eigenverbrauch (Ist ja das, was im Portal hinterlegt ist).

Wir können also einfach so vorgehen, wenn die openWB die Speichersteuerung übernehmen soll:

1. Modus "Immer":
Nix machen, Register 0xE004 ggf. auf 2 setzen.

2. Modus "Gesperrt, wenn Fahrzeug lädt":
Register 0xE004 auf 4 (Remote) setzen.
Register 0xE00A auf 7 (Max.Eigenverbrauch) setzen.
Register 0xE00D auf 7 (Max.Eigenverbrauch) setzen.
Register 0xE010 auf 0 (0W) setzen.
Register 0xE004 auf 2 (Time of Use) setzen, wenn die openWB die Speichersteuerung beendet.

3. Modus "Für Hausverbrauch":
Register 0xE004 auf 4 (Remote) setzen.
Register 0xE00A auf 7 (Max.Eigenverbrauch) setzen.
Register 0xE00D auf 7 (Max.Eigenverbrauch) setzen.
Register 0xE010 die Entladebegrenzung (aktueller Hausverbrauch in W) setzen.
Register 0xE004 auf 2 (Time of Use) setzen, wenn die openWB die Speichersteuerung beendet.

Damit ist sichergestellt, dass das Monitoringportal seine Profile oder die Einstellung Max.Eigenverbrauch trotzdem nutzen kann, wenn die openWB den Speicher gerade nicht steuert.
Max.Eigenverbrauch ist für die Speichersteuerung auch der einzig sinnvolle Modus, da hier ge- und entladen werden kann.
Wenn man bei 2. (Gesperrt) den Modus 0 nehmen würde, würde der Speicher komplett gesperrt sein, also auch keine Ladung aufnehmen, wenn mehr PV-Leistung zur Verfügung steht, als zum Laden benötigt wird. Das will bestimmt keiner. So wird nur die Entladung gesperrt.

Re: SolarEdge Speichersteuerung

Verfasst: Fr Feb 28, 2025 7:29 am
von Basti
Quck and dirty nach deiner Variante :

Code: Alles auswählen

#!/usr/bin/env python3
import logging
from typing import Dict, Tuple, Union, Optional

from pymodbus.constants import Endian

from dataclass_utils import dataclass_from_dict
from modules.common import modbus
from modules.common.abstract_device import AbstractBat
from modules.common.component_state import BatState
from modules.common.component_type import ComponentDescriptor
from modules.common.fault_state import ComponentInfo, FaultState
from modules.common.modbus import ModbusDataType
from modules.common.simcount import SimCounter
from modules.common.store import get_bat_value_store
from modules.devices.solaredge.solaredge.config import SolaredgeBatSetup

log = logging.getLogger(__name__)

FLOAT32_UNSUPPORTED = -0xffffff00000000000000000000000000

class SolaredgeBat(AbstractBat):
    """
    Klasse zur Verwaltung der Solaredge-Batteriesteuerung.
    Beinhaltet Funktionen zur Überwachung und Steuerung.
    """
    # Register-Adressen in Dezimal
    REMOTE_CONTROL_REGISTER = 57348      # 0xE004
    DISCHARGE_LIMIT_REGISTER = 57360       # 0xE010
    # Zusätzliche Register für Max.Eigenverbrauch
    MAX_EIGENVERBRAUCH_A_REGISTER = 57354  # 0xE00A
    MAX_EIGENVERBRAUCH_D_REGISTER = 57357  # 0xE00D
    # Commit Register
    COMMIT_REGISTER = 57741                # 0xE005 (Commit)

    def __init__(
        self, 
        device_id: int, 
        component_config: Union[Dict, SolaredgeBatSetup], 
        tcp_client: modbus.ModbusTcpClient_
    ) -> None:
        """
        Initialisiert die Batteriesteuerung.
        """
        self.__device_id = device_id
        self.component_config = dataclass_from_dict(SolaredgeBatSetup, component_config)
        self.__tcp_client = tcp_client
        self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher")
        self.store = get_bat_value_store(self.component_config.id)
        self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config))

    def update(self) -> None:
        """
        Aktualisiert den aktuellen Batteriestatus und speichert ihn im Value Store.
        """
        try:
            self.store.set(self.read_state())
        except Exception:
            log.exception("Fehler beim Aktualisieren des Batterie-Status")
            self.fault_state.set_fault("Batterie-Status konnte nicht aktualisiert werden")

    def read_state(self) -> BatState:
        """
        Liest den aktuellen Zustand der Batterie und gibt ihn als BatState zurück.
        """
        try:
            power, soc = self.get_values()
            imported, exported = self.get_imported_exported(power)
            return BatState(power=power, soc=soc, imported=imported, exported=exported)
        except Exception:
            log.exception("Fehler beim Lesen des Batterie-Zustands")
            self.fault_state.set_fault("Fehler beim Lesen des Batterie-Zustands")
            return BatState(power=0, soc=0, imported=0, exported=0)

    def get_values(self) -> Tuple[float, float]:
        """
        Liest SOC und Leistung aus den entsprechenden Modbus-Registern.
        """
        unit = self.component_config.configuration.modbus_id
        try:
            soc = self.__tcp_client.read_holding_registers(
                62852, ModbusDataType.FLOAT_32, wordorder=Endian.Little, unit=unit
            )
            power = self.__tcp_client.read_holding_registers(
                62836, ModbusDataType.FLOAT_32, wordorder=Endian.Little, unit=unit
            )
            if power is None or len(power) == 0 or power[0] == FLOAT32_UNSUPPORTED:
                power = 0
            return power, soc
        except Exception:
            log.exception("Fehler beim Abrufen der Werte aus den Registern")
            return 0, 0

    def get_imported_exported(self, power: float) -> Tuple[float, float]:
        """
        Berechnet importierte und exportierte Energie basierend auf der aktuellen Leistung.
        """
        return self.sim_counter.sim_count(power)

    def ensure_remote_control_mode(self, unit: int) -> bool:
        """
        Aktiviert den Remote Control Modus, falls nicht bereits aktiv.
        """
        try:
            current_mode = self.__tcp_client.read_holding_registers(
                self.REMOTE_CONTROL_REGISTER, ModbusDataType.INT_16, unit=unit
            )
            if current_mode and len(current_mode) > 0 and current_mode[0] == 4:
                log.debug("Remote control mode is already enabled.")
                return True
            log.info("Enabling remote control mode.")
            self.__tcp_client.write_registers(self.REMOTE_CONTROL_REGISTER, [4], unit=unit)
            self.commit_changes(unit)
            return True
        except Exception:
            log.exception("Error enabling remote control mode")
            return False

    def commit_changes(self, unit: int) -> None:
        """
        Bestätigt Änderungen durch Schreiben in das COMMIT-Register.
        """
        try:
            self.__tcp_client.write_registers(self.COMMIT_REGISTER, [1], unit=unit)
        except Exception:
            log.exception("Error committing changes")

    def set_power_limit(self, power_limit: Optional[Union[int, float]]) -> None:
        """
        Setzt das Entladeleistungs-Limit der Batterie innerhalb eines gültigen Bereichs.
        """
        unit = self.component_config.configuration.modbus_id
        if not self.ensure_remote_control_mode(unit):
            return
        if power_limit is None:
            power_limit = 5000
        if power_limit < 0 or power_limit > 5000:
            log.error(f"Invalid discharge limit: {power_limit}. Must be between 0 and 5000.")
            return
        try:
            self.__tcp_client.write_registers(self.DISCHARGE_LIMIT_REGISTER, [int(power_limit)], unit=unit)
            self.commit_changes(unit)
        except Exception:
            log.exception("Error setting discharge limit")

    def set_storage_mode(self, mode: str, discharge_limit: Optional[Union[int, float]] = None) -> None:
        """
        Setzt den gewünschten Speicherbetriebsmodus.

        Modi:
        1. "immer": Kein Eingriff, ggf. Register 0xE004 auf 2 (Time of Use) setzen.
        2. "gesperrt": (Gesperrt, wenn Fahrzeug lädt)
           - 0xE004 auf 4 (Remote)
           - 0xE00A auf 7 (Max.Eigenverbrauch)
           - 0xE00D auf 7 (Max.Eigenverbrauch)
           - 0xE010 auf 0 (keine Entladung)
        3. "hausverbrauch": (Für Hausverbrauch)
           - 0xE004 auf 4 (Remote)
           - 0xE00A auf 7 (Max.Eigenverbrauch)
           - 0xE00D auf 7 (Max.Eigenverbrauch)
           - 0xE010 auf den aktuellen Hausverbrauch in Watt (Entladebegrenzung)
        
        Wenn openWB die Steuerung beendet, sollte über release_storage_control() der Modus
        wieder auf 2 (Time of Use) zurückgesetzt werden.
        """
        unit = self.component_config.configuration.modbus_id
        try:
            if mode.lower() == "immer":
                # openWB greift nicht in die Steuerung ein – sicherheitshalber Time of Use aktivieren.
                self.__tcp_client.write_registers(self.REMOTE_CONTROL_REGISTER, [2], unit=unit)
                self.commit_changes(unit)
                log.info("Modus 'Immer' gesetzt: Steuerung nicht aktiv, Time of Use.")
            elif mode.lower() == "gesperrt":
                # Remote-Modus aktivieren und Profile auf Max.Eigenverbrauch setzen.
                self.__tcp_client.write_registers(self.REMOTE_CONTROL_REGISTER, [4], unit=unit)
                self.commit_changes(unit)
                self.__tcp_client.write_registers(self.MAX_EIGENVERBRAUCH_A_REGISTER, [7], unit=unit)
                self.commit_changes(unit)
                self.__tcp_client.write_registers(self.MAX_EIGENVERBRAUCH_D_REGISTER, [7], unit=unit)
                self.commit_changes(unit)
                # Entladung sperren: 0 Watt
                self.__tcp_client.write_registers(self.DISCHARGE_LIMIT_REGISTER, [0], unit=unit)
                self.commit_changes(unit)
                log.info("Modus 'Gesperrt, wenn Fahrzeug lädt' gesetzt.")
            elif mode.lower() == "hausverbrauch":
                # Remote-Modus aktivieren und Profile auf Max.Eigenverbrauch setzen.
                self.__tcp_client.write_registers(self.REMOTE_CONTROL_REGISTER, [4], unit=unit)
                self.commit_changes(unit)
                self.__tcp_client.write_registers(self.MAX_EIGENVERBRAUCH_A_REGISTER, [7], unit=unit)
                self.commit_changes(unit)
                self.__tcp_client.write_registers(self.MAX_EIGENVERBRAUCH_D_REGISTER, [7], unit=unit)
                self.commit_changes(unit)
                # Entladebegrenzung auf den aktuellen Hausverbrauch setzen
                if discharge_limit is None:
                    log.error("Für den 'Hausverbrauch'-Modus muss eine Entladebegrenzung angegeben werden.")
                    return
                self.__tcp_client.write_registers(self.DISCHARGE_LIMIT_REGISTER, [int(discharge_limit)], unit=unit)
                self.commit_changes(unit)
                log.info("Modus 'Für Hausverbrauch' gesetzt.")
            else:
                log.error(f"Unbekannter Modus: {mode}")
        except Exception:
            log.exception("Fehler beim Setzen des Speicherbetriebsmodus")

    def release_storage_control(self) -> None:
        """
        Beendet die Steuerung durch openWB, indem der Modus auf Time of Use (Wert 2) gesetzt wird.
        Dadurch kann das SolarEdge Monitoring-Portal seine Profile (z.B. Max.Eigenverbrauch) nutzen.
        """
        unit = self.component_config.configuration.modbus_id
        try:
            self.__tcp_client.write_registers(self.REMOTE_CONTROL_REGISTER, [2], unit=unit)
            self.commit_changes(unit)
            log.info("openWB-Steuerung beendet, Modus auf Time of Use (2) gesetzt.")
        except Exception:
            log.exception("Fehler beim Freigeben der Speichersteuerung")

component_descriptor = ComponentDescriptor(configuration_factory=SolaredgeBatSetup)
Wobei das hier ausreichen sollte , die Konkurrenz macht es nicht anders:

Code: Alles auswählen

#!/usr/bin/env python3
import logging
from typing import Dict, Tuple, Union, Optional

from pymodbus.constants import Endian

from dataclass_utils import dataclass_from_dict
from modules.common import modbus
from modules.common.abstract_device import AbstractBat
from modules.common.component_state import BatState
from modules.common.component_type import ComponentDescriptor
from modules.common.fault_state import ComponentInfo, FaultState
from modules.common.modbus import ModbusDataType
from modules.common.simcount import SimCounter
from modules.common.store import get_bat_value_store
from modules.devices.solaredge.solaredge.config import SolaredgeBatSetup

log = logging.getLogger(__name__)

FLOAT32_UNSUPPORTED = -0xffffff00000000000000000000000000


class SolaredgeBat(AbstractBat):
    """
    Klasse zur Verwaltung der Solaredge-Batteriesteuerung.
    Beinhaltet Funktionen zur Überwachung und Steuerung.
    """

    REMOTE_CONTROL_REGISTER = 57348  # Aktivierung von Remote Control
    ADVANCED_PWR_CTRL_REGISTER = 57740  # Aktivierung des erweiterten Leistungsmodus
    COMMIT_REGISTER = 57741  # Bestätigung von Änderungen
    DISCHARGE_LIMIT_REGISTER = 57360  # Leistungsbegrenzung in Watt

    def __init__(
        self, device_id: int, component_config: Union[Dict, SolaredgeBatSetup], tcp_client: modbus.ModbusTcpClient_
    ) -> None:
        """
        Initialisiert die Batteriesteuerung.
        """
        self.__device_id = device_id
        self.component_config = dataclass_from_dict(SolaredgeBatSetup, component_config)
        self.__tcp_client = tcp_client
        self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher")
        self.store = get_bat_value_store(self.component_config.id)
        self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config))

    def update(self) -> None:
        """
        Aktualisiert den aktuellen Batteriestatus und speichert ihn im Value Store.
        """
        try:
            self.store.set(self.read_state())
        except Exception:
            log.exception("Fehler beim Aktualisieren des Batterie-Status")
            self.fault_state.set_fault("Batterie-Status konnte nicht aktualisiert werden")

    def read_state(self) -> BatState:
        """
        Liest den aktuellen Zustand der Batterie und gibt ihn als BatState zurück.
        """
        try:
            power, soc = self.get_values()
            imported, exported = self.get_imported_exported(power)
            return BatState(power=power, soc=soc, imported=imported, exported=exported)
        except Exception:
            log.exception("Fehler beim Lesen des Batterie-Zustands")
            self.fault_state.set_fault("Fehler beim Lesen des Batterie-Zustands")
            return BatState(power=0, soc=0, imported=0, exported=0)

    def get_values(self) -> Tuple[float, float]:
        """
        Liest SOC und Leistung aus den entsprechenden Modbus-Registern.
        """
        unit = self.component_config.configuration.modbus_id
        try:
            soc = self.__tcp_client.read_holding_registers(
                62852, ModbusDataType.FLOAT_32, wordorder=Endian.Little, unit=unit
            )
            power = self.__tcp_client.read_holding_registers(
                62836, ModbusDataType.FLOAT_32, wordorder=Endian.Little, unit=unit
            )
            if power is None or len(power) == 0 or power[0] == FLOAT32_UNSUPPORTED:
                power = 0
            return power, soc
        except Exception:
            log.exception("Fehler beim Abrufen der Werte aus den Registern")
            return 0, 0

    def get_imported_exported(self, power: float) -> Tuple[float, float]:
        """
        Berechnet importierte und exportierte Energie basierend auf der aktuellen Leistung.
        """
        return self.sim_counter.sim_count(power)

    def ensure_remote_control_mode(self, unit: int) -> bool:
        """
        Aktiviert den Remote Control Modus, falls nicht bereits aktiv.
        """
        try:
            current_mode = self.__tcp_client.read_holding_registers(
                self.REMOTE_CONTROL_REGISTER, ModbusDataType.INT_16, unit=unit
            )
            if current_mode and len(current_mode) > 0 and current_mode[0] == 4:
                log.debug("Remote control mode is already enabled.")
                return True
            log.info("Enabling remote control mode.")
            self.__tcp_client.write_registers(self.REMOTE_CONTROL_REGISTER, [4], unit=unit)
            self.commit_changes(unit)
            return True
        except Exception:
            log.exception("Error enabling remote control mode")
            return False

    def commit_changes(self, unit: int) -> None:
        """
        Bestätigt Änderungen durch Schreiben in das COMMIT-Register.
        """
        try:
            self.__tcp_client.write_registers(self.COMMIT_REGISTER, [1], unit=unit)
        except Exception:
            log.exception("Error committing changes")

    def set_power_limit(self, power_limit: Optional[Union[int, float]]) -> None:
        """
        Setzt das Entladeleistungs-Limit der Batterie innerhalb eines gültigen Bereichs.
        """
        unit = self.component_config.configuration.modbus_id
        if not self.ensure_remote_control_mode(unit):
            return
        if power_limit is None:
            power_limit = 5000
        if power_limit < 0 or power_limit > 5000:
            log.error(f"Invalid discharge limit: {power_limit}. Must be between 0 and 5000.")
            return
        try:
            self.__tcp_client.write_registers(self.DISCHARGE_LIMIT_REGISTER, [int(power_limit)], unit=unit)
            self.commit_changes(unit)
        except Exception:
            log.exception("Error setting discharge limit")


component_descriptor = ComponentDescriptor(configuration_factory=SolaredgeBatSetup)

Re: SolarEdge Speichersteuerung

Verfasst: Fr Feb 28, 2025 6:47 pm
von ChristophR
Cool, kann damit am WE mal ein bisschen rumprobieren.

Ist ja nicht schlimm, wenn wir es besser machen als die Mitbewerber. ;-)
Wenn jemand über das SolarEdge Portal dann beispielsweise den Speicher laden möchte o.ä., funktioniert das nur, wenn der Control-Mode wieder freigegeben ist (2).

Ich habe noch folgende Beobachtungen gemacht:
- Ich habe nicht, wie in dem Dokument beschrieben die Werte für AdvancedPwrControlEn und ReactivePwrConfig verändert, es hat trotzdem funktioniert.
- Ich habe auch nie den Commit Befehl ausgeführt, ich hatte das so verstanden, dass das nur für das setzen von AdvancedPwrControlEn und ReactivePwrConfig nötig ist.

Ich weiß zwar nicht wirklich wie das funktioniert, habe in Deinem Code aber mal die Registervariablen an die Bezeichnungen von SolarEdge aus dem Dokument angepasst, suchen/ersetzen kriege ich noch hin ohne den Rest zu verstehen. :P

Code: Alles auswählen

#!/usr/bin/env python3
import logging
from typing import Dict, Tuple, Union, Optional

from pymodbus.constants import Endian

from dataclass_utils import dataclass_from_dict
from modules.common import modbus
from modules.common.abstract_device import AbstractBat
from modules.common.component_state import BatState
from modules.common.component_type import ComponentDescriptor
from modules.common.fault_state import ComponentInfo, FaultState
from modules.common.modbus import ModbusDataType
from modules.common.simcount import SimCounter
from modules.common.store import get_bat_value_store
from modules.devices.solaredge.solaredge.config import SolaredgeBatSetup

log = logging.getLogger(__name__)

FLOAT32_UNSUPPORTED = -0xffffff00000000000000000000000000

class SolaredgeBat(AbstractBat):
    """
    Klasse zur Verwaltung der Solaredge-Batteriesteuerung.
    Beinhaltet Funktionen zur Überwachung und Steuerung.
    """
    # Register-Adressen in Dezimal
    CONTROL_MODE_REGISTER = 57348      # 0xE004
    DISCHARGE_LIMIT_REGISTER = 57360       # 0xE010
    # Zusätzliche Register für Command-Mode (7 - Max.Eigenverbrauch)
    DEFAULT_MODE_REGISTER = 57354  # 0xE00A
    COMMAND_MODE_REGISTER = 57357  # 0xE00D
    # Commit Register
    COMMIT_REGISTER = 57741                # 0xE005 (Commit)

    def __init__(
        self, 
        device_id: int, 
        component_config: Union[Dict, SolaredgeBatSetup], 
        tcp_client: modbus.ModbusTcpClient_
    ) -> None:
        """
        Initialisiert die Batteriesteuerung.
        """
        self.__device_id = device_id
        self.component_config = dataclass_from_dict(SolaredgeBatSetup, component_config)
        self.__tcp_client = tcp_client
        self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher")
        self.store = get_bat_value_store(self.component_config.id)
        self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config))

    def update(self) -> None:
        """
        Aktualisiert den aktuellen Batteriestatus und speichert ihn im Value Store.
        """
        try:
            self.store.set(self.read_state())
        except Exception:
            log.exception("Fehler beim Aktualisieren des Batterie-Status")
            self.fault_state.set_fault("Batterie-Status konnte nicht aktualisiert werden")

    def read_state(self) -> BatState:
        """
        Liest den aktuellen Zustand der Batterie und gibt ihn als BatState zurück.
        """
        try:
            power, soc = self.get_values()
            imported, exported = self.get_imported_exported(power)
            return BatState(power=power, soc=soc, imported=imported, exported=exported)
        except Exception:
            log.exception("Fehler beim Lesen des Batterie-Zustands")
            self.fault_state.set_fault("Fehler beim Lesen des Batterie-Zustands")
            return BatState(power=0, soc=0, imported=0, exported=0)

    def get_values(self) -> Tuple[float, float]:
        """
        Liest SOC und Leistung aus den entsprechenden Modbus-Registern.
        """
        unit = self.component_config.configuration.modbus_id
        try:
            soc = self.__tcp_client.read_holding_registers(
                62852, ModbusDataType.FLOAT_32, wordorder=Endian.Little, unit=unit
            )
            power = self.__tcp_client.read_holding_registers(
                62836, ModbusDataType.FLOAT_32, wordorder=Endian.Little, unit=unit
            )
            if power is None or len(power) == 0 or power[0] == FLOAT32_UNSUPPORTED:
                power = 0
            return power, soc
        except Exception:
            log.exception("Fehler beim Abrufen der Werte aus den Registern")
            return 0, 0

    def get_imported_exported(self, power: float) -> Tuple[float, float]:
        """
        Berechnet importierte und exportierte Energie basierend auf der aktuellen Leistung.
        """
        return self.sim_counter.sim_count(power)

    def ensure_remote_control_mode(self, unit: int) -> bool:
        """
        Aktiviert den Remote Control Modus, falls nicht bereits aktiv.
        """
        try:
            current_mode = self.__tcp_client.read_holding_registers(
                self.CONTROL_MODE_REGISTER, ModbusDataType.INT_16, unit=unit
            )
            if current_mode and len(current_mode) > 0 and current_mode[0] == 4:
                log.debug("Remote control mode is already enabled.")
                return True
            log.info("Enabling remote control mode.")
            self.__tcp_client.write_registers(self.CONTROL_MODE_REGISTER, [4], unit=unit)
            self.commit_changes(unit)
            return True
        except Exception:
            log.exception("Error enabling remote control mode")
            return False

    def commit_changes(self, unit: int) -> None:
        """
        Bestätigt Änderungen durch Schreiben in das COMMIT-Register.
        """
        try:
            self.__tcp_client.write_registers(self.COMMIT_REGISTER, [1], unit=unit)
        except Exception:
            log.exception("Error committing changes")

    def set_power_limit(self, power_limit: Optional[Union[int, float]]) -> None:
        """
        Setzt das Entladeleistungs-Limit der Batterie innerhalb eines gültigen Bereichs.
        """
        unit = self.component_config.configuration.modbus_id
        if not self.ensure_remote_control_mode(unit):
            return
        if power_limit is None:
            power_limit = 5000
        if power_limit < 0 or power_limit > 5000:
            log.error(f"Invalid discharge limit: {power_limit}. Must be between 0 and 5000.")
            return
        try:
            self.__tcp_client.write_registers(self.DISCHARGE_LIMIT_REGISTER, [int(power_limit)], unit=unit)
            self.commit_changes(unit)
        except Exception:
            log.exception("Error setting discharge limit")

    def set_storage_mode(self, mode: str, discharge_limit: Optional[Union[int, float]] = None) -> None:
        """
        Setzt den gewünschten Speicherbetriebsmodus.

        Modi:
        1. "immer": Kein Eingriff, ggf. Register 0xE004 auf 2 (Time of Use) setzen.
        2. "gesperrt": (Gesperrt, wenn Fahrzeug lädt)
           - 0xE004 auf 4 (Remote)
           - 0xE00A auf 7 (Max.Eigenverbrauch)
           - 0xE00D auf 7 (Max.Eigenverbrauch)
           - 0xE010 auf 0 (keine Entladung)
        3. "hausverbrauch": (Für Hausverbrauch)
           - 0xE004 auf 4 (Remote)
           - 0xE00A auf 7 (Max.Eigenverbrauch)
           - 0xE00D auf 7 (Max.Eigenverbrauch)
           - 0xE010 auf den aktuellen Hausverbrauch in Watt (Entladebegrenzung)
        
        Wenn openWB die Steuerung beendet, sollte über release_storage_control() der Modus
        wieder auf 2 (Time of Use) zurückgesetzt werden.
        """
        unit = self.component_config.configuration.modbus_id
        try:
            if mode.lower() == "immer":
                # openWB greift nicht in die Steuerung ein – sicherheitshalber Time of Use aktivieren.
                self.__tcp_client.write_registers(self.CONTROL_MODE_REGISTER, [2], unit=unit)
                self.commit_changes(unit)
                log.info("Modus 'Immer' gesetzt: Steuerung nicht aktiv, Time of Use.")
            elif mode.lower() == "gesperrt":
                # Remote-Modus aktivieren und Profile auf Max.Eigenverbrauch setzen.
                self.__tcp_client.write_registers(self.CONTROL_MODE_REGISTER, [4], unit=unit)
                self.commit_changes(unit)
                self.__tcp_client.write_registers(self.DEFAULT_MODE_REGISTER, [7], unit=unit)
                self.commit_changes(unit)
                self.__tcp_client.write_registers(self.COMMAND_MODE_REGISTER, [7], unit=unit)
                self.commit_changes(unit)
                # Entladung sperren: 0 Watt
                self.__tcp_client.write_registers(self.DISCHARGE_LIMIT_REGISTER, [0], unit=unit)
                self.commit_changes(unit)
                log.info("Modus 'Gesperrt, wenn Fahrzeug lädt' gesetzt.")
            elif mode.lower() == "hausverbrauch":
                # Remote-Modus aktivieren und Profile auf Max.Eigenverbrauch setzen.
                self.__tcp_client.write_registers(self.CONTROL_MODE_REGISTER, [4], unit=unit)
                self.commit_changes(unit)
                self.__tcp_client.write_registers(self.DEFAULT_MODE_REGISTER, [7], unit=unit)
                self.commit_changes(unit)
                self.__tcp_client.write_registers(self.COMMAND_MODE_REGISTER, [7], unit=unit)
                self.commit_changes(unit)
                # Entladebegrenzung auf den aktuellen Hausverbrauch setzen
                if discharge_limit is None:
                    log.error("Für den 'Hausverbrauch'-Modus muss eine Entladebegrenzung angegeben werden.")
                    return
                self.__tcp_client.write_registers(self.DISCHARGE_LIMIT_REGISTER, [int(discharge_limit)], unit=unit)
                self.commit_changes(unit)
                log.info("Modus 'Für Hausverbrauch' gesetzt.")
            else:
                log.error(f"Unbekannter Modus: {mode}")
        except Exception:
            log.exception("Fehler beim Setzen des Speicherbetriebsmodus")

    def release_storage_control(self) -> None:
        """
        Beendet die Steuerung durch openWB, indem der Modus auf Time of Use (Wert 2) gesetzt wird.
        Dadurch kann das SolarEdge Monitoring-Portal seine Profile (z.B. Max.Eigenverbrauch) nutzen.
        """
        unit = self.component_config.configuration.modbus_id
        try:
            self.__tcp_client.write_registers(self.CONTROL_MODE_REGISTER, [2], unit=unit)
            self.commit_changes(unit)
            log.info("openWB-Steuerung beendet, Modus auf Time of Use (2) gesetzt.")
        except Exception:
            log.exception("Fehler beim Freigeben der Speichersteuerung")

component_descriptor = ComponentDescriptor(configuration_factory=SolaredgeBatSetup)
Dann habe ich noch folgende Fragen:
Wie oft wird denn der Command Mode gesetzt?
Läuft das nur einmal am Anfang eines Ladevorganges / der Speichersteuerung oder regelmäßig?
Im Standard ist der Timeout auf 1h (3600sec) gesetzt, danach wird der Default Mode genommen.
Die sind bei uns zwar identisch, ich weiß aber nicht, ob noch mehr nach Ablauf des Timeouts passiert, was dann stören könnte.

P.S: Auf Anhieb klappt es nicht gleich:

Code: Alles auswählen

2025-02-28 20:44:22,221 - {modules.devices.solaredge.solaredge.bat:91} - {ERROR:device0} - Fehler beim Abrufen der Werte aus den Registern
Traceback (most recent call last):
  File "/var/www/html/openWB/packages/modules/devices/solaredge/solaredge/bat.py", line 87, in get_values
    if power is None or len(power) == 0 or power[0] == FLOAT32_UNSUPPORTED:
TypeError: object of type 'float' has no len()

...

2025-02-28 20:44:23,277 - {modules.devices.solaredge.solaredge.bat:116} - {ERROR:set power limit 4} - Error enabling remote control mode
Traceback (most recent call last):
  File "/var/www/html/openWB/packages/modules/common/modbus.py", line 106, in __read_registers
    raise Exception(__name__+" "+str(response))
Exception: modules.common.modbus Modbus Error: [Input/Output] [Errno 104] Connection reset by peer

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/var/www/html/openWB/packages/modules/devices/solaredge/solaredge/bat.py", line 105, in ensure_remote_control_mode
    current_mode = self.__tcp_client.read_holding_registers(
  File "/var/www/html/openWB/packages/modules/common/modbus.py", line 138, in read_holding_registers
    return self.__read_registers(
  File "/var/www/html/openWB/packages/modules/common/modbus.py", line 121, in __read_registers
    raise Exception(__name__+" "+str(type(e))+" " + str(e)) from e
Exception: modules.common.modbus <class 'Exception'> modules.common.modbus Modbus Error: [Input/Output] [Errno 104] Connection reset by peer

Re: SolarEdge Speichersteuerung

Verfasst: Mo Mär 03, 2025 8:55 pm
von ChristophR
Ich habe am Wochenende mal probiert, die Lösung der SMA Speichersteuerung von mrinas umzubauen, dass sie auch für SolarEdge passt.
Bin damit zwar ein paar Schritte weiter gekommen, scheitere dann aber aufgrund meines Halb- bzw. Nichtwissens an der einen oder anderen Stelle.
Falls Interesse besteht, kann ich meinen Versuch hier gerne teilen, will dadurch aber auch nichts durcheinander bringen, was schon in Arbeit ist, daher lasse ich es lieber. ;)

Ich habe aber neue Erkenntnisse zum Verhalten von SolarEdge, wenn er gesteuert wird, im Speziellen die Entladebegrenzung.
Diese setzt sich nach einiger Zeit (Nicht nach dem Timeout) wieder auf den Standardwert 5000 zurück.
Ich glaube, dies passiert bei der Abendlichen Neuinitialisierung des SolarEdge Wechselrichters, wenn er kurz nicht erreichbar ist, werde das aber noch weiter beobachten und berichten:
2025-03-03 21_57_09-openWB und 3 weitere Seiten - Persönlich – Microsoft​ Edge.png
2025-03-03 21_57_09-openWB und 3 weitere Seiten - Persönlich – Microsoft​ Edge.png (24.81 KiB) 515 mal betrachtet

Re: SolarEdge Speichersteuerung

Verfasst: So Mär 09, 2025 1:13 am
von ChristophR
Ich habe nun folgenden Sachstand erreicht:
Es funktioniert nun im Prinzip, ich habe nur noch 1 unlösbares Problem. :cry:
Wenn die Modbus Register in der Definition (def set_power_limit(self, power_limit: Optional[int]) -> None:) aus der SolarEdge Batterie ausgelesen werden, kommt nach wenigen Abfragen "Connection Refused".
Dann werden nicht mal mehr die Zählerstände etc. aus dem SolarEdge aktualisiert, die komplette Kommunikation bricht dadurch zusammen.
Hinweis: openWB ist bei mir das einige System, was auf SolarEdge zugreift.
Wenn man den Regelmodus auf Sehr Langsam stellt (Das will natürlich keiner), klappen 2-3 Abfragen alle 4-5 Minuten.
Es liegt also nicht an der Häufigkeit der Abfragen.
Wenn ich die gleichen Register in der Definition (def read_state(self) -> BatState:) abfragen lasse, werden alle 10 Sekunden artig alle Register ausgelesen. Nur kann ich diese dann unter set_power_limit leider nicht einfach verwenden, auch wenn read_state immer vor set_power_limit läuft. Die Daten könnten also evtl. bereits bekannt sein.

Hier ist der Stand der bat.py.
Wie oben geschrieben, auf Basis der SMA Speichersteuerung, dort habe ich ungefähr die Abläufe verstanden oder erfragt, daher konnte ich es dann entsprechend umbauen:

Code: Alles auswählen

#!/usr/bin/env python3
import logging
from typing import Dict, Tuple, Union, Optional

from pymodbus.constants import Endian

from dataclass_utils import dataclass_from_dict
from modules.common import modbus
from modules.common.abstract_device import AbstractBat
from modules.common.component_state import BatState
from modules.common.component_type import ComponentDescriptor
from modules.common.fault_state import ComponentInfo, FaultState
from modules.common.modbus import ModbusDataType
from modules.common.simcount import SimCounter
from modules.common.store import get_bat_value_store
from modules.devices.solaredge.solaredge.config import SolaredgeBatSetup
import pymodbus

log = logging.getLogger(__name__)

FLOAT32_UNSUPPORTED = -0xffffff00000000000000000000000000

class SolaredgeBat(AbstractBat):
    # Define all possible registers with their data types
    REGISTERS = {
        "Battery1StateOfEnergy": (0xe184, ModbusDataType.FLOAT_32,),
        "Battery1InstantaneousPower": (0xe174, ModbusDataType.FLOAT_32,),
        "StorageControlMode": (0xe004, ModbusDataType.UINT_16,),
        "StorageChargeDischargeDefaultMode": (0xe00a, ModbusDataType.UINT_16,),
        "RemoteControlCommandMode": (0xe00d, ModbusDataType.UINT_16,),
        "RemoteControlCommandDischargeLimit": (0xe010, ModbusDataType.FLOAT_32,),
    }

    def __init__(self,
                 device_id: int,
                 component_config: Union[Dict, SolaredgeBatSetup],
                 tcp_client: modbus.ModbusTcpClient_) -> None:
        self.__device_id = device_id
        self.component_config = dataclass_from_dict(SolaredgeBatSetup, component_config)
        self.__tcp_client = tcp_client
        self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher")
        self.store = get_bat_value_store(self.component_config.id)
        self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config))
        self.last_mode = 'Unknown'

    def update(self) -> None:
        self.store.set(self.read_state())

    def read_state(self) -> BatState:
        unit = self.component_config.configuration.modbus_id

        registers_to_read = [
            "Battery1InstantaneousPower",
            "Battery1StateOfEnergy",
        ]
        values = self._read_registers(registers_to_read, unit)

        if values["Battery1InstantaneousPower"] == FLOAT32_UNSUPPORTED:
            values["Battery1InstantaneousPower"] = 0

        imported, exported = self.sim_counter.sim_count(values["Battery1InstantaneousPower"])

        bat_state = BatState(
            power=values["Battery1InstantaneousPower"],
            soc=values["Battery1StateOfEnergy"],
            imported=imported,
            exported=exported
        )
        log.debug(f"Bat {self.__tcp_client.address}: {bat_state}")
        return bat_state

    def set_power_limit(self, power_limit: Optional[int]) -> None:
        unit = self.component_config.configuration.modbus_id

        if power_limit is None and self.last_mode is None:
            # Kein Powerlimit gefordert, externe Steuerung bereits inaktiv
            log.debug("Keine Batteriesteuerung gefordert, externe Steuerung inaktiv.")

        elif power_limit is None and self.last_mode is not None:
            # Kein Powerlimit gefordert, externe Steuerung aktiv, externe Steuerung deaktivieren
            log.debug("Keine Batteriesteuerung mehr gefordert, deaktiviere externe Steuerung.")
            values_to_write = {
            "RemoteControlCommandDischargeLimit": 5000,
            "StorageControlMode": 2,
            }
            self._write_registers(values_to_write, unit)
            self.last_mode = None

        elif power_limit is not None:
            # Powerlimit gefordert, ggf. externe Steuerung aktivieren, Limit setzen
            registers_to_read = [
                "StorageControlMode",
                "RemoteControlCommandMode",
                "RemoteControlCommandDischargeLimit",
            ]
            values = self._read_registers(registers_to_read, unit)

            if values["StorageControlMode"] != 4 or values["RemoteControlCommandMode"] != 7:
                # externe Steuerung aktivieren, Standardwerte zur Steuerung setzen.
                values_to_write = {
                "StorageControlMode": 4,
                "StorageChargeDischargeDefaultMode": 7,
                "RemoteControlCommandMode": 7,
                }
                self._write_registers(values_to_write, unit)
                log.debug(f"Batteriesteuerung aktiviert.")
                self.last_mode = 'limited'

            if values["RemoteControlCommandDischargeLimit"] != power_limit:
                values_to_write = {
                "RemoteControlCommandDischargeLimit": int(min(power_limit, 5000))
                }
                self._write_registers(values_to_write, unit)
                log.debug(f"Powerlimit gesetzt {power_limit} W")

    def _read_registers(self, register_names: list, unit: int) -> Dict[str, Union[int, float]]:
        values = {}
        for key in register_names:
            log.debug(f"Bat raw values {self.__tcp_client.address}: {values}")
            address, data_type = self.REGISTERS[key]
            values[key] = self.__tcp_client.read_holding_registers(
            address, data_type, wordorder=Endian.Little, unit=unit
            )
        log.debug(f"Bat raw values {self.__tcp_client.address}: {values}")
        return values

    def _write_registers(self, values_to_write: Dict[str, Union[int, float]], unit: int) -> None:
        for key, value in values_to_write.items():
            address, data_type = self.REGISTERS[key]
            encoded_value = self._encode_value(value, data_type)
            self.__tcp_client.write_registers(address, encoded_value, unit=unit)
            log.debug(f"Neuer Wert {encoded_value} in Register {address} geschrieben.")

    def _encode_value(self, value: Union[int, float], data_type: ModbusDataType) -> list:
        builder = pymodbus.payload.BinaryPayloadBuilder(
                byteorder=pymodbus.constants.Endian.Big,
                wordorder=pymodbus.constants.Endian.Little
            )
        encode_methods = {
            ModbusDataType.UINT_32: builder.add_32bit_uint,
            ModbusDataType.INT_32: builder.add_32bit_int,
            ModbusDataType.UINT_16: builder.add_16bit_uint,
            ModbusDataType.INT_16: builder.add_16bit_int,
            ModbusDataType.FLOAT_32: builder.add_32bit_float,
        }

        if data_type in encode_methods:
            encode_methods[data_type](int(value))
        else:
            raise ValueError(f"Unsupported data type: {data_type}")

        return builder.to_registers()

component_descriptor = ComponentDescriptor(configuration_factory=SolaredgeBatSetup)

Re: SolarEdge Speichersteuerung

Verfasst: So Mär 09, 2025 8:07 am
von Elchkopp
Christoph, eine Frage dazu: ganz am Anfang hast Du ja über die Modi gesprochen, die der WR(?) einnehmen kann oder muss, damit es funktioniert (Maximaler Eigenverbrauch, Time od Use etc). Sind das die Modi, die ich auch über die normale SE-App einstellen kann? (Nicht die SetApp).

IMG_2173.jpeg
(618.57 KiB) Noch nie heruntergeladen

Re: SolarEdge Speichersteuerung

Verfasst: So Mär 09, 2025 9:53 am
von ChristophR
Elchkopp hat geschrieben: So Mär 09, 2025 8:07 am Christoph, eine Frage dazu: ganz am Anfang hast Du ja über die Modi gesprochen, die der WR(?) einnehmen kann oder muss, damit es funktioniert (Maximaler Eigenverbrauch, Time od Use etc). Sind das die Modi, die ich auch über die normale SE-App einstellen kann? (Nicht die SetApp).


IMG_2173.jpeg
Ja, genau.
Damit diese Einstellungen (oder die Deines Installateurs, wenn Du keine Änderungen vorgenommen hast) funktionieren, wenn openWB den Speicher nicht steuert, stellen wir auf "Time of Use" zurück.
Dann funktioniert auch "Maximaler Eigenverbrauch".