Seite 3 von 4

Re: Laden ohne die Hausbatterie zu leeren

Verfasst: Di Dez 17, 2024 5:28 pm
von Robeck
Gero hat geschrieben: Di Dez 17, 2024 9:11 am
Robeck hat geschrieben: Mo Dez 16, 2024 9:04 pm Bei mir mit E3DC ist nichts zu sehen, oder ich finde es einfach nicht.

Aber vielleicht findet sich ja jemand, der das für die openWB umsetzen mag.
Wir haben doch jetzt Weihnachten, da such doch mit Sicherheit einer von Euch eine Herausforderung ;) :mrgreen:
Ich kann sowas einfach nicht

Re: Laden ohne die Hausbatterie zu leeren

Verfasst: Fr Dez 20, 2024 4:54 pm
von Basti
So dank der neuen docu müsste es für solaredge jetzt funktionieren . Testen kann ich es selbst erst nach Weihnachten , auf eigene Gefahr 😜.

Code: Alles auswählen

bat.py ersetzen in Solaredge

Code: Alles auswählen

der Controll Mode 4 muss gesetzt werden , ich habe die Funktion auf dem Script jetzt entfernt , weil ich der Meinung bin das openwb keine Konfiguration ändern sollte am Fremdsystem , der Modus 4 Remote 0xE004 muss durch den User aktiv gesetzt werden 

Code: Alles auswählen

#!/usr/bin/env python3
"""
Dieses Skript ermöglicht die optionale Steuerung von SolarEdge-Batterien über Modbus. 
Für eine Fernsteuerung der Batterie muss der Wert des Registers StorageConf_CtrlMode (0xE004) auf "4" (Remote) gesetzt sein. 
Die Steuerung erfolgt über das Setzen von Leistungsbegrenzungen, Abfrage des Ladezustands (SOC) und der Batterieleistung.
"""

import logging
from typing import Optional, Dict, Union

from pymodbus.payload import BinaryPayloadDecoder, BinaryPayloadBuilder
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.simcount import SimCounter
from modules.common.store import get_bat_value_store
from modules.devices.solaredge.solaredge.config import SolaredgeBatSetup

# Initialisiere Logger für die Debug- und Fehlerausgabe
log = logging.getLogger(__name__)


class SolaredgeConfig:
    """
    Konfigurationsklasse für SolarEdge-Registeradressen. 
    Diese Klasse zentralisiert Adressen, um Änderungen einfach umzusetzen.
    """

    def __init__(self):
        # Register für Leistungsbegrenzung
        self.power_limit_register = 0xE010  # Maximal erlaubte Lade-/Entladeleistung
        # Register für den Ladezustand der Batterie (State of Charge - SOC)
        self.soc_register = 0xE184  # SOC in Prozent
        # Register für aktuelle Batterieleistung
        self.power_register = 0xE174  # Momentanleistung der Batterie


class SolaredgeBat(AbstractBat):
    """
    Klasse zur Steuerung eines SolarEdge-Speichersystems.
    Ermöglicht Modbus-Kommunikation zum Lesen und Schreiben von Parametern.
    """

    def __init__(self,
                 device_id: int,
                 component_config: Union[Dict, SolaredgeBatSetup],
                 tcp_client: modbus.ModbusTcpClient_,
                 config: SolaredgeConfig) -> None:
        """
        Initialisiert die SolarEdge-Batterie-Klasse.

        :param device_id: Eindeutige ID des Geräts für die Kommunikation.
        :param component_config: Konfigurationseinstellungen für die Batterie.
        :param tcp_client: Modbus-TCP-Client für die Kommunikation.
        :param config: Instanz der Konfigurationsklasse mit Registeradressen.
        """
        self.__device_id = device_id  # Geräte-ID
        self.component_config = dataclass_from_dict(SolaredgeBatSetup, component_config)
        self.__tcp_client = tcp_client  # Modbus-TCP-Client für Lese-/Schreiboperationen
        self.config = config  # SolarEdge-Registerkonfiguration
        self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher")
        self.store = get_bat_value_store(self.component_config.id)  # Interner Zustandsspeicher
        self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config))

    def set_power_limit(self, power_limit: Optional[int]) -> None:
        """
        Setzt die Leistungsbegrenzung für das Laden/Entladen.

        :param power_limit: Die gewünschte Begrenzung in Watt.
                            - Ein positiver Wert aktiviert die Steuerung.
                            - None oder -1 setzt die Begrenzung zurück (keine Limitierung).
        """
        unit = self.component_config.configuration.modbus_id
        register = self.config.power_limit_register

        try:
            if power_limit is None:
                power_limit = -1
                log.debug("Keine Leistungsbegrenzung angegeben. Begrenzung wird deaktiviert (-1).")

            # Versuch, den Wert direkt als INT32 zu schreiben
            try:
                current_limit = self.__tcp_client.read_holding_registers(register, ModbusDataType.INT_32, unit=unit)
                if current_limit == power_limit:
                    log.debug(f"Aktuelle Leistungsbegrenzung ({current_limit} W) entspricht dem Sollwert ({power_limit} W). Kein Schreibvorgang erforderlich.")
                    return
                self.__tcp_client.write_registers(register, power_limit, ModbusDataType.INT_32, unit=unit)
                log.info(f"Leistungsbegrenzung erfolgreich auf {power_limit} W gesetzt.")
            except Exception as e:
                log.warning(f"Direkte INT32-Verarbeitung fehlgeschlagen: {e}. Wechsel zu manueller Dekodierung.")

                # Fallback auf manuelle Zerlegung und Schreiben
                builder = BinaryPayloadBuilder(byteorder=Endian.Little, wordorder=Endian.Little)
                builder.add_32bit_int(power_limit)
                registers = builder.to_registers()
                self.__tcp_client.write_registers(register, registers, unit=unit)
                log.info(f"Leistungsbegrenzung erfolgreich (Fallback) auf {power_limit} W gesetzt.")
        except Exception as e:
            log.error(f"Fehler beim Setzen der Leistungsbegrenzung: {e}")
            raise

    def get_power_limit(self) -> Optional[int]:
        """
        Liest die aktuell eingestellte Leistungsbegrenzung.

        :return: Der aktuelle Wert der Leistungsbegrenzung in Watt (INT32).
        """
        unit = self.component_config.configuration.modbus_id
        register = self.config.power_limit_register

        try:
            # Direkt INT32 lesen
            try:
                current_limit = self.__tcp_client.read_holding_registers(register, ModbusDataType.INT_32, unit=unit)
                log.debug(f"Aktuelle Leistungsbegrenzung aus Register {register}: {current_limit} W")
                return current_limit
            except Exception as e:
                log.warning(f"Direkte INT32-Verarbeitung fehlgeschlagen: {e}. Wechsel zu manueller Dekodierung.")

                # Fallback auf manuelle Dekodierung
                response = self.__tcp_client.read_holding_registers(register, count=2, unit=unit)
                if response.isError():
                    raise Exception(f"Fehler beim Lesen der Leistungsbegrenzung aus Register {register}.")
                decoder = BinaryPayloadDecoder.fromRegisters(
                    response.registers, byteorder=Endian.Little, wordorder=Endian.Little
                )
                current_limit = decoder.decode_32bit_int()
                log.debug(f"Aktuelle Leistungsbegrenzung (Fallback) aus Register {register}: {current_limit} W")
                return current_limit
        except Exception as e:
            log.error(f"Fehler beim Lesen der Leistungsbegrenzung: {e}")
            raise


# Beschreibung der Konfigurationskomponente
component_descriptor = ComponentDescriptor(configuration_factory=SolaredgeBatSetup)

Re: Laden ohne die Hausbatterie zu leeren

Verfasst: Sa Dez 21, 2024 7:01 am
von Montair
Bei E3DC müsste das eigentlich recht einfach zu übernehmen sein, E3DC-Control kann das ja auch schon mWn. Vielleicht kann sich ein Entwickler mal mit Eberhard @eba-M via github kurzschließen?

Ich bin leider auch nicht für programmiertechnisches zu gebrauchen. Er hat ja schon mit E3DC-Control ein Steuerprogramm des E3DC auf die Beine gestellt, was auch die WB Steuerung übernimmt, und er ist sowieso dabei für seine Software eine openWB Übernahme mit aufzunehmen (meine seine Tochter hat eine openWB). Wenn er das von der einen Seite macht, kann man das sicherlich auch von der anderen Seite übernehmen.

https://github.com/Eba-M/E3DC-Control

Re: Laden ohne die Hausbatterie zu leeren

Verfasst: Sa Dez 21, 2024 8:57 am
von Gero
Die Herausforderung sind die unterschiedlichen Programmiersprachen. E3dc-Control basiert auf der Beispielimplementierung von e3dc und die ist in C. Das muss man kompilieren, die openWB ist in Skriptsprachen implementiert, die ohne kompilieren laufen.

Einer der Gründe, warum das in C ist, ist vielleicht die mitgelieferte AES-Bibliothek, die für die verschlüsselte Kommunikation mit dem S10 benötigt wird. Und das ist das, was ich mit erhöhtem Implementierungsaufwand meine. Für eine nahtlose Integration in die openWB müsste nicht nur die Ansteuerung an sich, sondern auch noch der AES-Part portiert werden. Oder man findet eine passende in Python o.ä. Oder man bei C, muss dafür dann aber die Installationsrroutine für das e3dc-Modul um eine Kompilier-Routine erweitern.

Wesentlich einfacher wäre eine REST-API, da kommt die Verschlüsselung über https und muss nicht separat implementiert werden.

Re: Laden ohne die Hausbatterie zu leeren

Verfasst: Sa Dez 21, 2024 3:55 pm
von Basti
So kleine Anpassung , es muss zuerst der Control Mode gesetzt werden . Erst dann können die Werte gesetzt werden. Ich habe das angepasst , das Schreiben erfolgt jetzt via float32 und little endian . Habe Kommentare hinzugefügt damit der Code besser lesbar ist und evtl. Leute helfen können .Adressen ausgelagert für die Übersicht .


Habt Nachsicht , ich mach das gerade alles am Handy 🙈

Re: Laden ohne die Hausbatterie zu leeren

Verfasst: Sa Dez 21, 2024 7:06 pm
von mrinas
Ich hätte auch eine Steuerung für SMA hybrid als PR fertig, mir fehlt aktuell lediglich die Sonne um das Testen zu können. Batterie ist zuletzt chronisch leer.

Re: Laden ohne die Hausbatterie zu leeren

Verfasst: So Dez 22, 2024 7:27 am
von Robeck
Ihr müsst E3DC machen, das ist der hot shit :mrgreen:

Spass beiseite, ich muss euch bewundern, für mich ist das chinesisch.....

Re: Laden ohne die Hausbatterie zu leeren

Verfasst: So Dez 22, 2024 8:44 am
von Basti
Naja theoretisch kannst du über Menü S10 auf sunspec umstellen und dann sollte auch das solaredge / sunspec Script funktionieren . Aber mit E3DC habe ich mich lange nicht mehr beschäftigt .

Ansonsten Python-e3dc über rscp

Re: Laden ohne die Hausbatterie zu leeren

Verfasst: So Dez 22, 2024 1:26 pm
von Gero
Basti hat geschrieben: So Dez 22, 2024 8:44 am Naja theoretisch kannst du über Menü S10 auf sunspec umstellen und dann sollte auch das solaredge / sunspec Script funktionieren .
Nur lesend. Schreiben geht nur per rscp.

Re: Laden ohne die Hausbatterie zu leeren

Verfasst: So Dez 22, 2024 3:10 pm
von hominidae
mrinas hat geschrieben: Sa Dez 21, 2024 7:06 pm Ich hätte auch eine Steuerung für SMA hybrid als PR fertig, mir fehlt aktuell lediglich die Sonne um das Testen zu können. Batterie ist zuletzt chronisch leer.
Vielleicht Off-Topic, aber bei SMA und sunspec muss man wohl aufpassen, welche Register beschrieben werden.
Im victron forum - da das ESS nun auch SMA -WR dynamisch regeln kann - ist das Thema aufgetaucht.
Wer die falschen Register zu oft nutzt, kann den Flash-Speicher des WR schrotten oder erreichen, das der WR sich tot stellt.

Ausserdem muss man evtl. aufpassen, ob der SMA Homemanager mit verbaut ist oder nicht, wenn man den WR ansprechen will.

Einige Infos, hier: https://community.victronenergy.com/t/v ... ry/14183/1