Skip to content
  • Recent
  • Tags
  • 0 Unread 0
  • Categories
  • Unreplied
  • Popular
  • GitHub
  • Docu
  • Hilfe
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Logo
  1. ioBroker Community Home
  2. Deutsch
  3. Einsteigerfragen
  4. Einbindung von Geräten
  5. Erfahrung: Hoymiles MS-A2 Akku via MQTT iobroker steuern

NEWS

  • UPDATE 31.10.: Amazon Alexa - ioBroker Skill läuft aus ?
    apollon77A
    apollon77
    48
    3
    8.0k

  • Monatsrückblick – September 2025
    BluefoxB
    Bluefox
    13
    1
    1.8k

  • Neues Video "KI im Smart Home" - ioBroker plus n8n
    BluefoxB
    Bluefox
    15
    1
    2.0k

Erfahrung: Hoymiles MS-A2 Akku via MQTT iobroker steuern

Erfahrung: Hoymiles MS-A2 Akku via MQTT iobroker steuern

Scheduled Pinned Locked Moved Einbindung von Geräten
akkuhoymilesmqtt
37 Posts 10 Posters 5.2k Views 10 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • BertDerKleineB Offline
    BertDerKleineB Offline
    BertDerKleine
    wrote on last edited by BertDerKleine
    #1

    Ich schildere hier mal, wie ich den Hoymiles MS-A2 standalone (kein WR, keine Balkonstrom-Panels, kein Shelly, kein Uni-Meter) per MQTT zum laufen bringe - als Nachrüstlösung für eine PV-Anlage.

    Vielleicht kann das ja jemand gebrauchen als Idee. Kann man bestimmt alles noch besser machen, aber als Basis kann ich sagen, dass es funktioniert. 😃

    Eventuell

    Hintergrund:
    Speziell für das Szenario, dass der MS-A2 standalone "als große Powerbank" betrieben werden soll, also ohne eingestöpselte Balkonstrom-Panels, braucht es eine Lade/Entladesteuerung, die auf Überschuß und Mangel reagiert.
    Seit Anfang Juni 2025 beherrscht der MS-A2 nativ MQTT, was nun eine noch einfachere Lösung ermöglicht, nämlich die direkte Steuerung über iobroker.

    Die offizielle Doku zum MQTT Feature findet man hier :

    • https://www.photovoltaikforum.com/core/file-download/507290/
    • https://www.photovoltaikforum.com/core/file-download/507291/
    • Der User "Rong" in dem Thread ist übrigens anscheinend ein Mitarbeiter des Herstelllers Hoymiles.

    Was braucht man also an Zutaten?

    • eine laufende iobroker Installation und einen eingestöpselten und mit dem WLAN verbundenen MS-A2, dessen Firmware über die Handy-App aktualisiert wurde.
    • einen MQTT Broker Adapter im iobroker
    • in der S-Miles App unter Zahnrad / MQTT-Service die Daten konfigurieren (Server IP Adresse des iobrokers, den Port (meist 1883) und Benutzernamen / Passwort.

    Dann tauscht der Akku mit iobroker Daten aus und im iobroker Objekt-Baum findet man unter mqtt / 0 / homeassistant nun die Datenpunkte.

    Standardmäßig lassen sich die Datenpunkte nur auslesen, aber nichts steuern. Um den Akku steuern zu können, muss man in mqtt.0.homeassistant.select.MSA-123456789.ems_mode.command manuell den Befehl mqtt_ctrl einmalig absetzen (der bleibt dann da).

    Die Steuerbefehle (Angaben in Watt) zum Laden/Entladen setzt man später in mqtt.0.homeassistant.number.MSA-123456789.power_ctrl.set ab. Dort stehen positive Werte für Entladen und negative für Laden des Akkus.

    Genutzte Aliase:

    Mein Steuerungsskript basiert auf ein paar Alias-Datenpunkten:

    • alias.0.Akku_befohlene_Entladeleistung --> mqtt.0.homeassistant.number.MSA-123456789.power_ctrl.set
    • alias.0.Akku_Ladezustand --> mqtt.0.homeassistant.sensor.MSA-123456789.quick.state mit Alias-Konverter beim Lesen JSON.parse(val).soc
    • alias.0.Akku_grid_on_power --> mqtt.0.homeassistant.sensor.MSA-123456789.quick.state mit Alias-Konverter beim Lesen JSON.parse(val).grid_on_p

    Mehr braucht es nicht. Man kann deutlich mehr Infos auslesen, aber zur Steuerung reicht das.

    Benötigte Datenquelle:

    Natürlich braucht es zusätzlich einen Datenpunkt im iobroker, der den aktuell vorliegenden Stromüberschuss im Haus liefert.

    In meinem Skript wird aktuell alias.0.Stromüberschuss_IR-Lesekopf verwendet, was den Überschuss direkt vom offiziellen Stromzähler ausliest (mittels eines Tasmota-basierten Infrarot Lesekopfs, den man sich leicht für kleines Geld beschaffen kann und der ebenfalls seine daten via  MQTT zu iobroker sendet). Diese Daten können aber auch von einem PV-Wechselrichter oder Stromzähler kommen, Hauptsache es ist der Stromüberschuss (positive Watt-Werte für einen Überschuss, d.h. Strom der sonst ins öffentliche Netz eingespeist wird und negative Werte für Netzbezug).

    Begrenzungen der Leistung und Entladung via App:

    Egal, was Ihr per MQTT an Befehlen sendet, die Lade- und Entladeleistung des Akkus hält sich an die Vorgaben, die Ihr via der S-Miles App eingegeben habt unter

    • Zahnrad / Einstellung der Ausgangsleistung (meist 800 W)
    • Zahnrad / Netzgebundene Eingangseinstellung (meist 800 W)
    • Zahnrad / Betriebsmodus / Eigenverbrauch / Entladeschlussstufe (meist 10%)

    Das ist im Skript alles auch mit abgefangen, aber m.W. passt der Akku da auch drauf auf.

    Besonderheit: Vergesslichkeit nach 59 Sekunden & Ignoranz bei immer gleichen Zahlen

    Es gibt eine Besonderheit, die zu beachten ist, nämlich vergisst der Akku nach 59 Sekunden den per MQTT vorgegebenen Befehl zum Laden/Entladen wieder. Und außerdem ignoriert er auch simple Wiederholungen einer Vorgabe der exakt selben Wattzahl.

    Sobald er diese Eure Vorgabe vergessen hat, führt der Akku das aus, was unter Zahnrad / Hauslasteinstellungen / Lastleistungskurve / Kurve 1 / (alle Tage und Uhrzeiten) in der App hinterlegt ist. Falls da also z.B. 100 W steht, wird der Akku dann mit 100 W entladen. Man kann in der App hier auch einfach "0 W" hinterlegen, so dass der Akku dann nix macht.

    Aus diesem Grund sendet mein Skript alle 30 Sekunden eine neue Wattvorgabe und zusätzlich alterniert diese immer um 0,1 Watt. So wird die Steuerung aufrechterhalten.

    Skript:

    Und hier findet Ihr mein Javascript als Denkanstoß.
    Ein großer Teil des Skripts ist Doku, Logging und Fehlerfindung gewidmet, man kann es also auch kürzen.

    /**
     * AKKU-MANAGEMENT-SKRIPT FÜR IO BROKER
     * 
     * Dieses Skript steuert einen Batteriespeicher basierend auf dem aktuellen Stromüberschuss.
     * Es lädt den Akku bei PV-Überschuss und entlädt ihn bei Strombedarf.
     * Entwurf von BertDerKleine
     * 
     * Merkmale:
     * - Kontinuierliche Regelung unabhängig von Tageszeit
     * - Getrennte Zielfaktoren für Lade- und Entladevorgänge
     * - PI-Regler für stabile Regelung
     * - SOC-Schutz gegen Überladung und Tiefentladung
     * - Alternierende Steuerbefehle für zuverlässige Akku-Kommunikation
     * - Ausführliche Diagnosefunktionen
     * - Konfigurierbares Logging
     */
    
    // ========== KONFIGURATION ==========
    const AUSFUEHRLICHES_LOGGING = false;  // Auf TRUE setzen für detaillierte Regel-Logs (nur für Debugging)
    const MAX_LOG_LAENGE = 100;             // Maximale Anzahl gespeicherter Log-Einträge für Diagnose
    
    // ========== GLOBALE VARIABLEN ==========
    /**
     * Intervall für das Senden von Steuerbefehlen (in Millisekunden)
     * Der Akku benötigt regelmäßige Aktualisierungen, da er Befehle nach 60-90 Sekunden "vergisst"
     * 30000 = 30 Sekunden ist ein bewährter Wert für zuverlässige Kommunikation
     */
    const alternierIntervall = 30000;
    
    /**
     * Datenpunkt für Akku-Steuerbefehle
     * - Positive Werte: Entladen (Leistungsabgabe)
     * - Negative Werte: Laden (Leistungsaufnahme)
     */
    const AKTIONS_DATENPUNKT = 'alias.0.Akku_befohlene_Entladeleistung';
    
    /** Datenpunkt für die aktuell gemessene Akku-Leistung */
    const AKTUELLE_AKKULEISTUNG = 'alias.0.Akku_grid_on_power';
    
    // Betriebsstatus
    let regelungAktiv = false;  // Gibt an, ob die Regelung aktuell läuft
    
    // Intervalle
    let messIntervall = null;    // Intervall für Leistungsmessungen
    let regelIntervall = null;   // Intervall für Regelberechnungen
    let sendeIntervall = null;   // Intervall für Steuerbefehle
    
    // Regelvariablen
    let mittelungArray = [];     // Speichert die letzten Messwerte für die Mittelwertbildung
    let aktuellerSollwert = 0;   // Aktuell berechneter Sollwert für Akku-Leistung
    let toggleFlag = true;       // Steuert die Alternierung der Steuerbefehle (+0.1W Wechsel)
    let integral = 0;            // Integralanteil des PI-Reglers
    
    // ========== PARAMETER ==========
    /**
     * Maximale Ladeleistung in Watt
     * Sollte unterhalb der technischen Grenzen des Akkus liegen.
     */
    const maxLadeleistung = 800;
    
    /**
     * Maximale Entladeleistung in Watt
     * Abhängig von Akku-Kapazität und Wechselrichter.
     */
    const maxEntladeleistung = 800;
    
    /**
     * Intervall für Leistungsmessungen in Millisekunden
     * 10000 = 10 Sekunden ist ein guter Kompromiss zwischen Aktualität und Stabilität
     */
    const samplingIntervall = 10000;
    
    /**
     * Faktor für den Integralanteil des PI-Reglers
     * Ein höherer Wert reagiert stärker auf anhaltende Abweichungen
     * 0.2 ist ein moderater Wert für stabile Regelung ohne zu starkes Überschwingen
     */
    const integralFaktor = 0.2;
    
    /** Ladezustand bei dem das Laden gestoppt wird (Vermeidung von Überladung) */
    const SOC_LADESTOPP = 98;
    
    /** Ladezustand bei dem das Entladen gestoppt wird (Vermeidung von Tiefentladung) */
    const SOC_ENTLADESTOPP = 10;
    
    /**
     * Zielfaktoren für Regelung
     * 
     * zielFaktorLaden: Anteil des Überschusses der fürs Laden genutzt wird (0-1)
     *   - 0.8 = 80% des Überschusses werden zum Laden genutzt (konservativ)
     *   - Höhere Werte nutzen mehr Überschuss, können aber zu Netzinstabilität führen
     * 
     * zielFaktorEntladen: Anteil des Bedarfs der durch Entladen gedeckt wird (0-1)
     *   - 1.0 = 100% des Bedarfs werden durch Entladen gedeckt (maximale Autarkie)
     *   - Niedrigere Werte schonen den Akku, erhöhen aber Netzbezug
     */
    const zielFaktorLaden = 0.8;
    const zielFaktorEntladen = 1.0;
    
    // ========== LOGGING SYSTEM ==========
    let logHistorie = [];  // Speichert die letzten Log-Einträge für Diagnosezwecke
    
    /**
     * Verbesserte Log-Funktion mit Historie-Speicherung und Level-Steuerung
     * 
     * @param {string} nachricht - Die zu loggende Nachricht
     * @param {string} level - Log-Level ('debug', 'info', 'warn', 'error')
     */
    function log(nachricht, level = 'info') {
        // Erstelle formatierten Log-Eintrag
        const timestamp = new Date().toISOString();
        const logEintrag = `[${timestamp}] [${level.toUpperCase()}] ${nachricht}`;
        
        // Füge zur Historie hinzu (begrenzt auf MAX_LOG_LAENGE)
        logHistorie.push(logEintrag);
        if (logHistorie.length > MAX_LOG_LAENGE) {
            logHistorie.shift();
        }
        
        // Ausgabe basierend auf Level und Konfiguration
        switch(level) {
            case 'error':
                console.error(logEintrag);
                break;
            case 'warn':
                console.warn(logEintrag);
                break;
            case 'info':
                console.log(logEintrag);
                break;
            case 'debug':
                // Debug-Logs nur bei aktiviertem ausführlichen Logging
                if (AUSFUEHRLICHES_LOGGING) {
                    console.log(logEintrag);
                }
                break;
            default:
                console.log(logEintrag);
        }
    }
    
    // ========== HELFERFUNKTIONEN ==========
    /**
     * Setzt den Steuerungsdatenpunkt mit Fehlerbehandlung
     * 
     * @param {number} wert - Der zu setzende Wert
     * @returns {boolean} True bei Erfolg, False bei Fehler
     */
    function sicherSetState(wert) {
        try {
            setState(AKTIONS_DATENPUNKT, wert, false);
            return true;
        } catch (e) {
            log(`Fehler beim Setzen des Datenpunkts: ${e.message}`, 'error');
            return false;
        }
    }
    
    /**
     * Bereinigt den gemessenen Überschuss um den aktuellen Akku-Beitrag
     * 
     * @param {number} ueberschussGemessen - Gemessener Stromüberschuss
     * @param {number} akkuLeistung - Aktuelle Akku-Leistung (positiv = Entladen, negativ = Laden)
     * @returns {number} Bereinigter Überschuss
     */
    function bereinigeLeistung(ueberschussGemessen, akkuLeistung) {
        return ueberschussGemessen - akkuLeistung;
    }
    
    // ========== FLEXIBLE REGELUNG ==========
    /**
     * Startet die kontinuierliche Akku-Regelung
     */
    function starteFlexibleRegelung() {
        if (regelungAktiv) {
            log("Regelung bereits aktiv - Start abgebrochen", 'debug');
            return;
        }
        
        stoppeRegelung();
        log("Starte flexible Regelung...");
        
        regelungAktiv = true;
        mittelungArray = [];
        aktuellerSollwert = 0;
        integral = 0;
        toggleFlag = true;
    
        // Initialer Status-Log mit Konfiguration
        log(`Regelung gestartet mit:
      Max Laden: ${maxLadeleistung}W
      Max Entladen: ${maxEntladeleistung}W
      Laden-Zielfaktor: ${zielFaktorLaden}
      Entladen-Zielfaktor: ${zielFaktorEntladen}
      SOC-Limits: Ladestopp >${SOC_LADESTOPP}%, Entladestopp <${SOC_ENTLADESTOPP}%`, 'info');
    
        // Messintervall - Sammelt regelmäßig Leistungsdaten
        messIntervall = setInterval(() => {
            try {
                const ueberschussGemessen = getState('alias.0.Stromüberschuss_IR-Lesekopf').val;
                const akkuLeistung = getState(AKTUELLE_AKKULEISTUNG).val;
                const bereinigteLeistung = bereinigeLeistung(ueberschussGemessen, akkuLeistung);
                
                mittelungArray.push(bereinigteLeistung);
                if (mittelungArray.length > 6) mittelungArray.shift();
            } catch (e) {
                log("Messfehler: " + e.message, 'error');
            }
        }, samplingIntervall);
    
        // Regelintervall - Berechnet alle 20 Sekunden neue Sollwerte
        regelIntervall = setInterval(() => {
            try {
                if (mittelungArray.length < 3) {
                    log("Nicht genug Messwerte für Regelung", 'debug');
                    return;
                }
                
                // Berechne gleitenden Mittelwert der letzten Messungen
                const summe = mittelungArray.reduce((a, b) => a + b, 0);
                const mittelwert = summe / mittelungArray.length;
                
                // Bestimmung des Betriebsmodus
                const istLadebetrieb = mittelwert > 0; // Positiver Wert = Überschuss = Laden
                const zielFaktor = istLadebetrieb ? zielFaktorLaden : zielFaktorEntladen;
                
                // Berechnung der benötigten Akku-Leistung
                const zielLeistung = Math.abs(mittelwert) * zielFaktor;
                const zielwert = istLadebetrieb ? -zielLeistung : zielLeistung;
                
                // PI-Regler berechnet Anpassung
                const differenz = zielwert - aktuellerSollwert;
                integral += differenz * integralFaktor;
                
                // Integrator-Begrenzung
                const integratorLimit = istLadebetrieb ? maxLadeleistung : maxEntladeleistung;
                integral = Math.min(Math.max(integral, -integratorLimit), integratorLimit);
                
                // Neuen Sollwert berechnen
                let neuerSollwert = aktuellerSollwert + differenz + integral;
                
                // Physikalische Grenzen einhalten
                if (istLadebetrieb) {
                    neuerSollwert = Math.min(neuerSollwert, 0); // Max 0 = keine Entladung
                    neuerSollwert = Math.max(neuerSollwert, -maxLadeleistung); // Min = -maxLadeleistung
                } else {
                    neuerSollwert = Math.max(neuerSollwert, 0); // Min 0 = kein Laden
                    neuerSollwert = Math.min(neuerSollwert, maxEntladeleistung); // Max = maxEntladeleistung
                }
                
                // Sanfte Anpassung (30% pro Schritt)
                const aenderung = neuerSollwert - aktuellerSollwert;
                if (Math.abs(aenderung) > 10) {
                    aktuellerSollwert += aenderung * 0.3;
                } else {
                    aktuellerSollwert = neuerSollwert;
                }
                
                // Ausführliches Logging nur bei Bedarf
                if (AUSFUEHRLICHES_LOGGING) {
                    log(`Regelberechnung:
      Überschuss (gemittelt): ${mittelwert.toFixed(1)}W
      Ziel-Leistung: ${zielLeistung.toFixed(1)}W
      Zielwert (Akku): ${zielwert.toFixed(1)}W
      Neuer Sollwert: ${neuerSollwert.toFixed(1)}W
      Aktueller Sollwert: ${aktuellerSollwert.toFixed(1)}W
      Differenz: ${differenz.toFixed(1)}W
      Integral: ${integral.toFixed(1)}
      Faktor: ${zielFaktor}
      Modus: ${istLadebetrieb ? 'Laden' : 'Entladen'}`, 'debug');
                }
            } catch (e) {
                log("Regelfehler: " + e.message, 'error');
            }
        }, 20000);
    
        // Sendeintervall - Sendet alle 30 Sekunden Steuerbefehle
        sendeIntervall = setInterval(() => {
            if (!regelungAktiv) return;
            
            // SOC-Schutz - Verhindert Überladung und Tiefentladung
            const akkuSOC = getState('alias.0.Akku_Ladezustand').val;
            let effektiverSollwert = aktuellerSollwert;
            
            if (effektiverSollwert < 0 && akkuSOC > SOC_LADESTOPP) {
                effektiverSollwert = 0;
                log(`LADESTOPP: Akku > ${SOC_LADESTOPP}% (SOC: ${akkuSOC.toFixed(1)}%)`, 'warn');
            }
            
            if (effektiverSollwert > 0 && akkuSOC < SOC_ENTLADESTOPP) {
                effektiverSollwert = 0;
                log(`ENTLADESTOPP: Akku < ${SOC_ENTLADESTOPP}% (SOC: ${akkuSOC.toFixed(1)}%)`, 'warn');
            }
            
            // Alternierende Werte senden (jeder 2. Befehl +0.1W)
            const wert = toggleFlag ? effektiverSollwert : effektiverSollwert + 0.1;
            sicherSetState(wert);
            toggleFlag = !toggleFlag;
            
            // Protokolliere nur die ersten 20 Minuten oder bei Moduswechsel
            log(`Steuerbefehl gesendet: ${wert.toFixed(1)}W (${toggleFlag ? 'nächstes Mal Basiswert' : 'nächstes Mal +0.1W'})`, 'debug');
        }, alternierIntervall);
    
        log("Flexible Regelung erfolgreich gestartet", 'info');
    }
    
    /**
     * Stoppt die laufende Regelung
     */
    function stoppeRegelung() {
        if (!regelungAktiv) return;
        
        clearInterval(messIntervall);
        clearInterval(regelIntervall);
        clearInterval(sendeIntervall);
        regelungAktiv = false;
        
        sicherSetState(0);
        log("Regelung gestoppt", 'info');
    }
    
    // ========== HAUPTPROGRAMM ==========
    /**
     * Initialisiert das Skript
     */
    function initSkript() {
        try {
            starteFlexibleRegelung();
            log("Skript initialisierung abgeschlossen");
            
            // Aktiviere temporäres ausführliches Logging für die ersten 10 Minuten
            setTimeout(() => {
                if (!AUSFUEHRLICHES_LOGGING) {
                    log("Initiale Debug-Periode beendet - Detaillogs deaktiviert");
                }
            }, 600000); // 10 Minuten
        } catch (e) {
            log("Initialisierungsfehler: " + e.message, 'error');
        }
    }
    
    // Startverzögerung für Systeminitialisierung
    setTimeout(initSkript, 5000);
    
    // ========== EVENT-HANDLER ==========
    // Überwacht Ladezustandsänderungen
    on({ id: 'alias.0.Akku_Ladezustand', change: 'ne' }, (state) => {
        const soc = state.val;
        
        // Protokolliere nur bei relevanten Änderungen
        if (soc > SOC_LADESTOPP && aktuellerSollwert < 0) {
            log(`AKTION: Ladung pausiert (SOC: ${soc}% > ${SOC_LADESTOPP}%)`, 'warn');
        } else if (soc < SOC_ENTLADESTOPP && aktuellerSollwert > 0) {
            log(`AKTION: Entladung pausiert (SOC: ${soc}% < ${SOC_ENTLADESTOPP}%)`, 'warn');
        } else if (soc <= SOC_LADESTOPP && soc >= SOC_ENTLADESTOPP) {
            log(`SOC im normalen Bereich: ${soc}%`, 'debug');
        }
    });
    
    // ========== DIAGNOSE & MONITORING ==========
    // Regelmäßige Systemdiagnose
    const diagIntervall = setInterval(() => {
        const ueberschuss = getState('alias.0.Stromüberschuss_IR-Lesekopf').val || 0;
        const akkuLeistung = getState(AKTUELLE_AKKULEISTUNG).val || 0;
        const akkuSOC = getState('alias.0.Akku_Ladezustand').val || 0;
        const istLadebetrieb = aktuellerSollwert < 0;
        
        log(`Systemdiagnose:
      Regelstatus: ${regelungAktiv ? "AKTIV" : "INAKTIV"}
      Betriebsmodus: ${istLadebetrieb ? 'LADEN' : 'ENTLADEN'}
      Sollleistung: ${aktuellerSollwert.toFixed(1)}W
      Istleistung: ${akkuLeistung}W
      Überschuss: ${ueberschuss}W
      SOC: ${akkuSOC.toFixed(1)}%
      Messwerte: ${mittelungArray.length} gespeichert
      Letzte Logs: ${logHistorie.slice(-3).join('\n  ')}`, 
      'info');
    }, 300000); // Alle 5 Minuten
    
    // Datenpunkt-Prüfung
    function pruefeDatenpunkte() {
        const datenpunkte = [
            'alias.0.Stromüberschuss_IR-Lesekopf',
            'alias.0.Akku_Ladezustand',
            AKTUELLE_AKKULEISTUNG,
            AKTIONS_DATENPUNKT
        ];
        
        let fehler = false;
        datenpunkte.forEach(id => {
            if (getState(id) === undefined) {
                fehler = true;
                log(`KRITISCH: Datenpunkt ${id} nicht gefunden!`, 'error');
            }
        });
        
        if (fehler) {
            log("Fehlende Datenpunkte - Skriptfunktionalität eingeschränkt", 'error');
        } else {
            log("Alle benötigten Datenpunkte verfügbar", 'debug');
        }
    }
    
    // Verzögerte Datenpunktprüfung
    setTimeout(pruefeDatenpunkte, 15000);
    
    // ========== NOTFALL-SYSTEM ==========
    // Automatischer Neustart bei Inaktivität
    const watchdogIntervall = setInterval(() => {
        if (!regelungAktiv) {
            log("Watchdog: Regelung inaktiv - Neustart wird versucht", 'warn');
            initSkript();
        }
    }, 600000); // Prüft alle 10 Minuten
    
    B H A 3 Replies Last reply
    0
    • BertDerKleineB BertDerKleine

      Ich schildere hier mal, wie ich den Hoymiles MS-A2 standalone (kein WR, keine Balkonstrom-Panels, kein Shelly, kein Uni-Meter) per MQTT zum laufen bringe - als Nachrüstlösung für eine PV-Anlage.

      Vielleicht kann das ja jemand gebrauchen als Idee. Kann man bestimmt alles noch besser machen, aber als Basis kann ich sagen, dass es funktioniert. 😃

      Eventuell

      Hintergrund:
      Speziell für das Szenario, dass der MS-A2 standalone "als große Powerbank" betrieben werden soll, also ohne eingestöpselte Balkonstrom-Panels, braucht es eine Lade/Entladesteuerung, die auf Überschuß und Mangel reagiert.
      Seit Anfang Juni 2025 beherrscht der MS-A2 nativ MQTT, was nun eine noch einfachere Lösung ermöglicht, nämlich die direkte Steuerung über iobroker.

      Die offizielle Doku zum MQTT Feature findet man hier :

      • https://www.photovoltaikforum.com/core/file-download/507290/
      • https://www.photovoltaikforum.com/core/file-download/507291/
      • Der User "Rong" in dem Thread ist übrigens anscheinend ein Mitarbeiter des Herstelllers Hoymiles.

      Was braucht man also an Zutaten?

      • eine laufende iobroker Installation und einen eingestöpselten und mit dem WLAN verbundenen MS-A2, dessen Firmware über die Handy-App aktualisiert wurde.
      • einen MQTT Broker Adapter im iobroker
      • in der S-Miles App unter Zahnrad / MQTT-Service die Daten konfigurieren (Server IP Adresse des iobrokers, den Port (meist 1883) und Benutzernamen / Passwort.

      Dann tauscht der Akku mit iobroker Daten aus und im iobroker Objekt-Baum findet man unter mqtt / 0 / homeassistant nun die Datenpunkte.

      Standardmäßig lassen sich die Datenpunkte nur auslesen, aber nichts steuern. Um den Akku steuern zu können, muss man in mqtt.0.homeassistant.select.MSA-123456789.ems_mode.command manuell den Befehl mqtt_ctrl einmalig absetzen (der bleibt dann da).

      Die Steuerbefehle (Angaben in Watt) zum Laden/Entladen setzt man später in mqtt.0.homeassistant.number.MSA-123456789.power_ctrl.set ab. Dort stehen positive Werte für Entladen und negative für Laden des Akkus.

      Genutzte Aliase:

      Mein Steuerungsskript basiert auf ein paar Alias-Datenpunkten:

      • alias.0.Akku_befohlene_Entladeleistung --> mqtt.0.homeassistant.number.MSA-123456789.power_ctrl.set
      • alias.0.Akku_Ladezustand --> mqtt.0.homeassistant.sensor.MSA-123456789.quick.state mit Alias-Konverter beim Lesen JSON.parse(val).soc
      • alias.0.Akku_grid_on_power --> mqtt.0.homeassistant.sensor.MSA-123456789.quick.state mit Alias-Konverter beim Lesen JSON.parse(val).grid_on_p

      Mehr braucht es nicht. Man kann deutlich mehr Infos auslesen, aber zur Steuerung reicht das.

      Benötigte Datenquelle:

      Natürlich braucht es zusätzlich einen Datenpunkt im iobroker, der den aktuell vorliegenden Stromüberschuss im Haus liefert.

      In meinem Skript wird aktuell alias.0.Stromüberschuss_IR-Lesekopf verwendet, was den Überschuss direkt vom offiziellen Stromzähler ausliest (mittels eines Tasmota-basierten Infrarot Lesekopfs, den man sich leicht für kleines Geld beschaffen kann und der ebenfalls seine daten via  MQTT zu iobroker sendet). Diese Daten können aber auch von einem PV-Wechselrichter oder Stromzähler kommen, Hauptsache es ist der Stromüberschuss (positive Watt-Werte für einen Überschuss, d.h. Strom der sonst ins öffentliche Netz eingespeist wird und negative Werte für Netzbezug).

      Begrenzungen der Leistung und Entladung via App:

      Egal, was Ihr per MQTT an Befehlen sendet, die Lade- und Entladeleistung des Akkus hält sich an die Vorgaben, die Ihr via der S-Miles App eingegeben habt unter

      • Zahnrad / Einstellung der Ausgangsleistung (meist 800 W)
      • Zahnrad / Netzgebundene Eingangseinstellung (meist 800 W)
      • Zahnrad / Betriebsmodus / Eigenverbrauch / Entladeschlussstufe (meist 10%)

      Das ist im Skript alles auch mit abgefangen, aber m.W. passt der Akku da auch drauf auf.

      Besonderheit: Vergesslichkeit nach 59 Sekunden & Ignoranz bei immer gleichen Zahlen

      Es gibt eine Besonderheit, die zu beachten ist, nämlich vergisst der Akku nach 59 Sekunden den per MQTT vorgegebenen Befehl zum Laden/Entladen wieder. Und außerdem ignoriert er auch simple Wiederholungen einer Vorgabe der exakt selben Wattzahl.

      Sobald er diese Eure Vorgabe vergessen hat, führt der Akku das aus, was unter Zahnrad / Hauslasteinstellungen / Lastleistungskurve / Kurve 1 / (alle Tage und Uhrzeiten) in der App hinterlegt ist. Falls da also z.B. 100 W steht, wird der Akku dann mit 100 W entladen. Man kann in der App hier auch einfach "0 W" hinterlegen, so dass der Akku dann nix macht.

      Aus diesem Grund sendet mein Skript alle 30 Sekunden eine neue Wattvorgabe und zusätzlich alterniert diese immer um 0,1 Watt. So wird die Steuerung aufrechterhalten.

      Skript:

      Und hier findet Ihr mein Javascript als Denkanstoß.
      Ein großer Teil des Skripts ist Doku, Logging und Fehlerfindung gewidmet, man kann es also auch kürzen.

      /**
       * AKKU-MANAGEMENT-SKRIPT FÜR IO BROKER
       * 
       * Dieses Skript steuert einen Batteriespeicher basierend auf dem aktuellen Stromüberschuss.
       * Es lädt den Akku bei PV-Überschuss und entlädt ihn bei Strombedarf.
       * Entwurf von BertDerKleine
       * 
       * Merkmale:
       * - Kontinuierliche Regelung unabhängig von Tageszeit
       * - Getrennte Zielfaktoren für Lade- und Entladevorgänge
       * - PI-Regler für stabile Regelung
       * - SOC-Schutz gegen Überladung und Tiefentladung
       * - Alternierende Steuerbefehle für zuverlässige Akku-Kommunikation
       * - Ausführliche Diagnosefunktionen
       * - Konfigurierbares Logging
       */
      
      // ========== KONFIGURATION ==========
      const AUSFUEHRLICHES_LOGGING = false;  // Auf TRUE setzen für detaillierte Regel-Logs (nur für Debugging)
      const MAX_LOG_LAENGE = 100;             // Maximale Anzahl gespeicherter Log-Einträge für Diagnose
      
      // ========== GLOBALE VARIABLEN ==========
      /**
       * Intervall für das Senden von Steuerbefehlen (in Millisekunden)
       * Der Akku benötigt regelmäßige Aktualisierungen, da er Befehle nach 60-90 Sekunden "vergisst"
       * 30000 = 30 Sekunden ist ein bewährter Wert für zuverlässige Kommunikation
       */
      const alternierIntervall = 30000;
      
      /**
       * Datenpunkt für Akku-Steuerbefehle
       * - Positive Werte: Entladen (Leistungsabgabe)
       * - Negative Werte: Laden (Leistungsaufnahme)
       */
      const AKTIONS_DATENPUNKT = 'alias.0.Akku_befohlene_Entladeleistung';
      
      /** Datenpunkt für die aktuell gemessene Akku-Leistung */
      const AKTUELLE_AKKULEISTUNG = 'alias.0.Akku_grid_on_power';
      
      // Betriebsstatus
      let regelungAktiv = false;  // Gibt an, ob die Regelung aktuell läuft
      
      // Intervalle
      let messIntervall = null;    // Intervall für Leistungsmessungen
      let regelIntervall = null;   // Intervall für Regelberechnungen
      let sendeIntervall = null;   // Intervall für Steuerbefehle
      
      // Regelvariablen
      let mittelungArray = [];     // Speichert die letzten Messwerte für die Mittelwertbildung
      let aktuellerSollwert = 0;   // Aktuell berechneter Sollwert für Akku-Leistung
      let toggleFlag = true;       // Steuert die Alternierung der Steuerbefehle (+0.1W Wechsel)
      let integral = 0;            // Integralanteil des PI-Reglers
      
      // ========== PARAMETER ==========
      /**
       * Maximale Ladeleistung in Watt
       * Sollte unterhalb der technischen Grenzen des Akkus liegen.
       */
      const maxLadeleistung = 800;
      
      /**
       * Maximale Entladeleistung in Watt
       * Abhängig von Akku-Kapazität und Wechselrichter.
       */
      const maxEntladeleistung = 800;
      
      /**
       * Intervall für Leistungsmessungen in Millisekunden
       * 10000 = 10 Sekunden ist ein guter Kompromiss zwischen Aktualität und Stabilität
       */
      const samplingIntervall = 10000;
      
      /**
       * Faktor für den Integralanteil des PI-Reglers
       * Ein höherer Wert reagiert stärker auf anhaltende Abweichungen
       * 0.2 ist ein moderater Wert für stabile Regelung ohne zu starkes Überschwingen
       */
      const integralFaktor = 0.2;
      
      /** Ladezustand bei dem das Laden gestoppt wird (Vermeidung von Überladung) */
      const SOC_LADESTOPP = 98;
      
      /** Ladezustand bei dem das Entladen gestoppt wird (Vermeidung von Tiefentladung) */
      const SOC_ENTLADESTOPP = 10;
      
      /**
       * Zielfaktoren für Regelung
       * 
       * zielFaktorLaden: Anteil des Überschusses der fürs Laden genutzt wird (0-1)
       *   - 0.8 = 80% des Überschusses werden zum Laden genutzt (konservativ)
       *   - Höhere Werte nutzen mehr Überschuss, können aber zu Netzinstabilität führen
       * 
       * zielFaktorEntladen: Anteil des Bedarfs der durch Entladen gedeckt wird (0-1)
       *   - 1.0 = 100% des Bedarfs werden durch Entladen gedeckt (maximale Autarkie)
       *   - Niedrigere Werte schonen den Akku, erhöhen aber Netzbezug
       */
      const zielFaktorLaden = 0.8;
      const zielFaktorEntladen = 1.0;
      
      // ========== LOGGING SYSTEM ==========
      let logHistorie = [];  // Speichert die letzten Log-Einträge für Diagnosezwecke
      
      /**
       * Verbesserte Log-Funktion mit Historie-Speicherung und Level-Steuerung
       * 
       * @param {string} nachricht - Die zu loggende Nachricht
       * @param {string} level - Log-Level ('debug', 'info', 'warn', 'error')
       */
      function log(nachricht, level = 'info') {
          // Erstelle formatierten Log-Eintrag
          const timestamp = new Date().toISOString();
          const logEintrag = `[${timestamp}] [${level.toUpperCase()}] ${nachricht}`;
          
          // Füge zur Historie hinzu (begrenzt auf MAX_LOG_LAENGE)
          logHistorie.push(logEintrag);
          if (logHistorie.length > MAX_LOG_LAENGE) {
              logHistorie.shift();
          }
          
          // Ausgabe basierend auf Level und Konfiguration
          switch(level) {
              case 'error':
                  console.error(logEintrag);
                  break;
              case 'warn':
                  console.warn(logEintrag);
                  break;
              case 'info':
                  console.log(logEintrag);
                  break;
              case 'debug':
                  // Debug-Logs nur bei aktiviertem ausführlichen Logging
                  if (AUSFUEHRLICHES_LOGGING) {
                      console.log(logEintrag);
                  }
                  break;
              default:
                  console.log(logEintrag);
          }
      }
      
      // ========== HELFERFUNKTIONEN ==========
      /**
       * Setzt den Steuerungsdatenpunkt mit Fehlerbehandlung
       * 
       * @param {number} wert - Der zu setzende Wert
       * @returns {boolean} True bei Erfolg, False bei Fehler
       */
      function sicherSetState(wert) {
          try {
              setState(AKTIONS_DATENPUNKT, wert, false);
              return true;
          } catch (e) {
              log(`Fehler beim Setzen des Datenpunkts: ${e.message}`, 'error');
              return false;
          }
      }
      
      /**
       * Bereinigt den gemessenen Überschuss um den aktuellen Akku-Beitrag
       * 
       * @param {number} ueberschussGemessen - Gemessener Stromüberschuss
       * @param {number} akkuLeistung - Aktuelle Akku-Leistung (positiv = Entladen, negativ = Laden)
       * @returns {number} Bereinigter Überschuss
       */
      function bereinigeLeistung(ueberschussGemessen, akkuLeistung) {
          return ueberschussGemessen - akkuLeistung;
      }
      
      // ========== FLEXIBLE REGELUNG ==========
      /**
       * Startet die kontinuierliche Akku-Regelung
       */
      function starteFlexibleRegelung() {
          if (regelungAktiv) {
              log("Regelung bereits aktiv - Start abgebrochen", 'debug');
              return;
          }
          
          stoppeRegelung();
          log("Starte flexible Regelung...");
          
          regelungAktiv = true;
          mittelungArray = [];
          aktuellerSollwert = 0;
          integral = 0;
          toggleFlag = true;
      
          // Initialer Status-Log mit Konfiguration
          log(`Regelung gestartet mit:
        Max Laden: ${maxLadeleistung}W
        Max Entladen: ${maxEntladeleistung}W
        Laden-Zielfaktor: ${zielFaktorLaden}
        Entladen-Zielfaktor: ${zielFaktorEntladen}
        SOC-Limits: Ladestopp >${SOC_LADESTOPP}%, Entladestopp <${SOC_ENTLADESTOPP}%`, 'info');
      
          // Messintervall - Sammelt regelmäßig Leistungsdaten
          messIntervall = setInterval(() => {
              try {
                  const ueberschussGemessen = getState('alias.0.Stromüberschuss_IR-Lesekopf').val;
                  const akkuLeistung = getState(AKTUELLE_AKKULEISTUNG).val;
                  const bereinigteLeistung = bereinigeLeistung(ueberschussGemessen, akkuLeistung);
                  
                  mittelungArray.push(bereinigteLeistung);
                  if (mittelungArray.length > 6) mittelungArray.shift();
              } catch (e) {
                  log("Messfehler: " + e.message, 'error');
              }
          }, samplingIntervall);
      
          // Regelintervall - Berechnet alle 20 Sekunden neue Sollwerte
          regelIntervall = setInterval(() => {
              try {
                  if (mittelungArray.length < 3) {
                      log("Nicht genug Messwerte für Regelung", 'debug');
                      return;
                  }
                  
                  // Berechne gleitenden Mittelwert der letzten Messungen
                  const summe = mittelungArray.reduce((a, b) => a + b, 0);
                  const mittelwert = summe / mittelungArray.length;
                  
                  // Bestimmung des Betriebsmodus
                  const istLadebetrieb = mittelwert > 0; // Positiver Wert = Überschuss = Laden
                  const zielFaktor = istLadebetrieb ? zielFaktorLaden : zielFaktorEntladen;
                  
                  // Berechnung der benötigten Akku-Leistung
                  const zielLeistung = Math.abs(mittelwert) * zielFaktor;
                  const zielwert = istLadebetrieb ? -zielLeistung : zielLeistung;
                  
                  // PI-Regler berechnet Anpassung
                  const differenz = zielwert - aktuellerSollwert;
                  integral += differenz * integralFaktor;
                  
                  // Integrator-Begrenzung
                  const integratorLimit = istLadebetrieb ? maxLadeleistung : maxEntladeleistung;
                  integral = Math.min(Math.max(integral, -integratorLimit), integratorLimit);
                  
                  // Neuen Sollwert berechnen
                  let neuerSollwert = aktuellerSollwert + differenz + integral;
                  
                  // Physikalische Grenzen einhalten
                  if (istLadebetrieb) {
                      neuerSollwert = Math.min(neuerSollwert, 0); // Max 0 = keine Entladung
                      neuerSollwert = Math.max(neuerSollwert, -maxLadeleistung); // Min = -maxLadeleistung
                  } else {
                      neuerSollwert = Math.max(neuerSollwert, 0); // Min 0 = kein Laden
                      neuerSollwert = Math.min(neuerSollwert, maxEntladeleistung); // Max = maxEntladeleistung
                  }
                  
                  // Sanfte Anpassung (30% pro Schritt)
                  const aenderung = neuerSollwert - aktuellerSollwert;
                  if (Math.abs(aenderung) > 10) {
                      aktuellerSollwert += aenderung * 0.3;
                  } else {
                      aktuellerSollwert = neuerSollwert;
                  }
                  
                  // Ausführliches Logging nur bei Bedarf
                  if (AUSFUEHRLICHES_LOGGING) {
                      log(`Regelberechnung:
        Überschuss (gemittelt): ${mittelwert.toFixed(1)}W
        Ziel-Leistung: ${zielLeistung.toFixed(1)}W
        Zielwert (Akku): ${zielwert.toFixed(1)}W
        Neuer Sollwert: ${neuerSollwert.toFixed(1)}W
        Aktueller Sollwert: ${aktuellerSollwert.toFixed(1)}W
        Differenz: ${differenz.toFixed(1)}W
        Integral: ${integral.toFixed(1)}
        Faktor: ${zielFaktor}
        Modus: ${istLadebetrieb ? 'Laden' : 'Entladen'}`, 'debug');
                  }
              } catch (e) {
                  log("Regelfehler: " + e.message, 'error');
              }
          }, 20000);
      
          // Sendeintervall - Sendet alle 30 Sekunden Steuerbefehle
          sendeIntervall = setInterval(() => {
              if (!regelungAktiv) return;
              
              // SOC-Schutz - Verhindert Überladung und Tiefentladung
              const akkuSOC = getState('alias.0.Akku_Ladezustand').val;
              let effektiverSollwert = aktuellerSollwert;
              
              if (effektiverSollwert < 0 && akkuSOC > SOC_LADESTOPP) {
                  effektiverSollwert = 0;
                  log(`LADESTOPP: Akku > ${SOC_LADESTOPP}% (SOC: ${akkuSOC.toFixed(1)}%)`, 'warn');
              }
              
              if (effektiverSollwert > 0 && akkuSOC < SOC_ENTLADESTOPP) {
                  effektiverSollwert = 0;
                  log(`ENTLADESTOPP: Akku < ${SOC_ENTLADESTOPP}% (SOC: ${akkuSOC.toFixed(1)}%)`, 'warn');
              }
              
              // Alternierende Werte senden (jeder 2. Befehl +0.1W)
              const wert = toggleFlag ? effektiverSollwert : effektiverSollwert + 0.1;
              sicherSetState(wert);
              toggleFlag = !toggleFlag;
              
              // Protokolliere nur die ersten 20 Minuten oder bei Moduswechsel
              log(`Steuerbefehl gesendet: ${wert.toFixed(1)}W (${toggleFlag ? 'nächstes Mal Basiswert' : 'nächstes Mal +0.1W'})`, 'debug');
          }, alternierIntervall);
      
          log("Flexible Regelung erfolgreich gestartet", 'info');
      }
      
      /**
       * Stoppt die laufende Regelung
       */
      function stoppeRegelung() {
          if (!regelungAktiv) return;
          
          clearInterval(messIntervall);
          clearInterval(regelIntervall);
          clearInterval(sendeIntervall);
          regelungAktiv = false;
          
          sicherSetState(0);
          log("Regelung gestoppt", 'info');
      }
      
      // ========== HAUPTPROGRAMM ==========
      /**
       * Initialisiert das Skript
       */
      function initSkript() {
          try {
              starteFlexibleRegelung();
              log("Skript initialisierung abgeschlossen");
              
              // Aktiviere temporäres ausführliches Logging für die ersten 10 Minuten
              setTimeout(() => {
                  if (!AUSFUEHRLICHES_LOGGING) {
                      log("Initiale Debug-Periode beendet - Detaillogs deaktiviert");
                  }
              }, 600000); // 10 Minuten
          } catch (e) {
              log("Initialisierungsfehler: " + e.message, 'error');
          }
      }
      
      // Startverzögerung für Systeminitialisierung
      setTimeout(initSkript, 5000);
      
      // ========== EVENT-HANDLER ==========
      // Überwacht Ladezustandsänderungen
      on({ id: 'alias.0.Akku_Ladezustand', change: 'ne' }, (state) => {
          const soc = state.val;
          
          // Protokolliere nur bei relevanten Änderungen
          if (soc > SOC_LADESTOPP && aktuellerSollwert < 0) {
              log(`AKTION: Ladung pausiert (SOC: ${soc}% > ${SOC_LADESTOPP}%)`, 'warn');
          } else if (soc < SOC_ENTLADESTOPP && aktuellerSollwert > 0) {
              log(`AKTION: Entladung pausiert (SOC: ${soc}% < ${SOC_ENTLADESTOPP}%)`, 'warn');
          } else if (soc <= SOC_LADESTOPP && soc >= SOC_ENTLADESTOPP) {
              log(`SOC im normalen Bereich: ${soc}%`, 'debug');
          }
      });
      
      // ========== DIAGNOSE & MONITORING ==========
      // Regelmäßige Systemdiagnose
      const diagIntervall = setInterval(() => {
          const ueberschuss = getState('alias.0.Stromüberschuss_IR-Lesekopf').val || 0;
          const akkuLeistung = getState(AKTUELLE_AKKULEISTUNG).val || 0;
          const akkuSOC = getState('alias.0.Akku_Ladezustand').val || 0;
          const istLadebetrieb = aktuellerSollwert < 0;
          
          log(`Systemdiagnose:
        Regelstatus: ${regelungAktiv ? "AKTIV" : "INAKTIV"}
        Betriebsmodus: ${istLadebetrieb ? 'LADEN' : 'ENTLADEN'}
        Sollleistung: ${aktuellerSollwert.toFixed(1)}W
        Istleistung: ${akkuLeistung}W
        Überschuss: ${ueberschuss}W
        SOC: ${akkuSOC.toFixed(1)}%
        Messwerte: ${mittelungArray.length} gespeichert
        Letzte Logs: ${logHistorie.slice(-3).join('\n  ')}`, 
        'info');
      }, 300000); // Alle 5 Minuten
      
      // Datenpunkt-Prüfung
      function pruefeDatenpunkte() {
          const datenpunkte = [
              'alias.0.Stromüberschuss_IR-Lesekopf',
              'alias.0.Akku_Ladezustand',
              AKTUELLE_AKKULEISTUNG,
              AKTIONS_DATENPUNKT
          ];
          
          let fehler = false;
          datenpunkte.forEach(id => {
              if (getState(id) === undefined) {
                  fehler = true;
                  log(`KRITISCH: Datenpunkt ${id} nicht gefunden!`, 'error');
              }
          });
          
          if (fehler) {
              log("Fehlende Datenpunkte - Skriptfunktionalität eingeschränkt", 'error');
          } else {
              log("Alle benötigten Datenpunkte verfügbar", 'debug');
          }
      }
      
      // Verzögerte Datenpunktprüfung
      setTimeout(pruefeDatenpunkte, 15000);
      
      // ========== NOTFALL-SYSTEM ==========
      // Automatischer Neustart bei Inaktivität
      const watchdogIntervall = setInterval(() => {
          if (!regelungAktiv) {
              log("Watchdog: Regelung inaktiv - Neustart wird versucht", 'warn');
              initSkript();
          }
      }, 600000); // Prüft alle 10 Minuten
      
      B Offline
      B Offline
      besimo
      wrote on last edited by
      #2

      @bertderkleine
      Kann man den MS-A2 im Mqtt-Mode in den Standby zwingen z.B. durch Set Power = 0 W ?
      Hat jemand den Eigenverbrauch gemessen bei Power = 0 W ?

      BertDerKleineB 2 Replies Last reply
      0
      • B besimo

        @bertderkleine
        Kann man den MS-A2 im Mqtt-Mode in den Standby zwingen z.B. durch Set Power = 0 W ?
        Hat jemand den Eigenverbrauch gemessen bei Power = 0 W ?

        BertDerKleineB Offline
        BertDerKleineB Offline
        BertDerKleine
        wrote on last edited by
        #3

        @besimo
        Wenn Du in "mqtt.0.homeassistant.number.MSA-123456789.power_ctrl.set" eine Null sendest, dann macht er halt nix (lädt nicht & entlädt nicht), was für mich "Standby" bedeutet".

        Den Eigenverbrauch kenne ich nicht.

        1 Reply Last reply
        0
        • B Offline
          B Offline
          besimo
          wrote on last edited by
          #4

          @bertderkleine
          Danke für die schnelle Antwort. Also eine schleichende Reduzierung des SOC, wenn set power einige Stunden auf 0 stand, ist dir nicht aufgefallen ?

          Zum Vergleich beim EZHI von APsystems hat ein Nutzer von einer Reduzierung des SOC von 14% auf 8% in der Nacht, nachdem abends der eingestellte min SOC erreicht war, berichtet.

          BertDerKleineB 1 Reply Last reply
          0
          • B besimo

            @bertderkleine
            Danke für die schnelle Antwort. Also eine schleichende Reduzierung des SOC, wenn set power einige Stunden auf 0 stand, ist dir nicht aufgefallen ?

            Zum Vergleich beim EZHI von APsystems hat ein Nutzer von einer Reduzierung des SOC von 14% auf 8% in der Nacht, nachdem abends der eingestellte min SOC erreicht war, berichtet.

            BertDerKleineB Offline
            BertDerKleineB Offline
            BertDerKleine
            wrote on last edited by
            #5

            @besimo
            Ich kann zumindest sagen, dass der Akku wirklich nicht unter die eingestellte "Entladeschlussstufe" fällt, wenn er die mal erreicht hat.
            Darum kümmert sich die fest eingebaute Logik anscheinend.

            Was er da an Energie reinsteckt, weiss ich nicht. Ich gehe davon aus, dass die Geräte hier einen klar messbaren Eigenverbrauch haben.

            Siehe: https://www.photovoltaik.eu/solarspeicher/wie-viel-schluckt-der-speicher

            Also auch bei den teureren und größeren Gerätschaften sind 15 W nichts überraschendes.

            Je nachdem wo man den Hoymiles hinstellt, gibt es im Winter auch noch die Stromkosten für die eingebaute "Heizung", die an sich ein sinnvolles Feature ist.

            B 1 Reply Last reply
            0
            • BertDerKleineB BertDerKleine

              @besimo
              Ich kann zumindest sagen, dass der Akku wirklich nicht unter die eingestellte "Entladeschlussstufe" fällt, wenn er die mal erreicht hat.
              Darum kümmert sich die fest eingebaute Logik anscheinend.

              Was er da an Energie reinsteckt, weiss ich nicht. Ich gehe davon aus, dass die Geräte hier einen klar messbaren Eigenverbrauch haben.

              Siehe: https://www.photovoltaik.eu/solarspeicher/wie-viel-schluckt-der-speicher

              Also auch bei den teureren und größeren Gerätschaften sind 15 W nichts überraschendes.

              Je nachdem wo man den Hoymiles hinstellt, gibt es im Winter auch noch die Stromkosten für die eingebaute "Heizung", die an sich ein sinnvolles Feature ist.

              B Offline
              B Offline
              besimo
              wrote on last edited by besimo
              #6

              @bertderkleine
              Angeblich kann man in der App Langzeitspeicherung unterer SOC z.B. 10% einstellen, dann macht er keine Erhaltungsladung, wenn der SOC unter 10% fällt. Ist Langzeitspeicherung nicht eingestellt, macht er dann eine Erhaltungsladung.
              Hast du die Langzeitspeicherung aktiviert ?

              Grundsätzlich ist der MS-A2 ja sehr sparsam (3W im Standby) im Vergleich zu den grossen Speichern. Aber erreicht er das auch bei aktiviertem Mqtt ?

              Und Heizung ist kein Thema, die Temperatur kann man überwachen. Fällt sie unter 1°, muss man nur den Aufstellort ins Haus verlegen.

              Mein Ziel ist ja, im Winter auch noch einen bestmöglichen Autarkiegrad zu erreichen. Einen Fehlkauf habe ich ja schon mit der Huawei Luna2000-S1-10kWh. Da gehen jeden Tag von den 10kWh Nettokapazität 2kWh für den Eigenverbrauch drauf, also effektiv bleiben 8kWh echte Nettokapazität.

              BertDerKleineB 1 Reply Last reply
              0
              • B besimo

                @bertderkleine
                Angeblich kann man in der App Langzeitspeicherung unterer SOC z.B. 10% einstellen, dann macht er keine Erhaltungsladung, wenn der SOC unter 10% fällt. Ist Langzeitspeicherung nicht eingestellt, macht er dann eine Erhaltungsladung.
                Hast du die Langzeitspeicherung aktiviert ?

                Grundsätzlich ist der MS-A2 ja sehr sparsam (3W im Standby) im Vergleich zu den grossen Speichern. Aber erreicht er das auch bei aktiviertem Mqtt ?

                Und Heizung ist kein Thema, die Temperatur kann man überwachen. Fällt sie unter 1°, muss man nur den Aufstellort ins Haus verlegen.

                Mein Ziel ist ja, im Winter auch noch einen bestmöglichen Autarkiegrad zu erreichen. Einen Fehlkauf habe ich ja schon mit der Huawei Luna2000-S1-10kWh. Da gehen jeden Tag von den 10kWh Nettokapazität 2kWh für den Eigenverbrauch drauf, also effektiv bleiben 8kWh echte Nettokapazität.

                BertDerKleineB Offline
                BertDerKleineB Offline
                BertDerKleine
                wrote on last edited by
                #7

                @besimo sagte in Erfahrung: Hoymiles MS-A2 Akku via MQTT iobroker steuern:

                Hast du die Langzeitspeicherung aktiviert ?

                Was meinst Du damit? Es gibt in der App nichts mit dem Namen.

                Ich habe (wie oben beschrieben) Zahnrad / Betriebsmodus / Eigenverbrauch / Entladeschlussstufe auf 10% gesetzt bzw. dieses Default so stehengelassen.

                B 1 Reply Last reply
                0
                • BertDerKleineB BertDerKleine

                  @besimo sagte in Erfahrung: Hoymiles MS-A2 Akku via MQTT iobroker steuern:

                  Hast du die Langzeitspeicherung aktiviert ?

                  Was meinst Du damit? Es gibt in der App nichts mit dem Namen.

                  Ich habe (wie oben beschrieben) Zahnrad / Betriebsmodus / Eigenverbrauch / Entladeschlussstufe auf 10% gesetzt bzw. dieses Default so stehengelassen.

                  B Offline
                  B Offline
                  besimo
                  wrote on last edited by
                  #8

                  @bertderkleine
                  Ich glaube, da hat chatgpt wieder Unwahrheiten geschrieben. Die Option Langzeitspeicherung scheint es wohl doch nicht zu geben. Die Erhaltungsladung kann bei Erreichen min SOC nur Manuell durch Ziehen des Schukostecker vermieden werden.
                  Evtl. gäbe es noch die Option über den Zeitplan in der App auf kein Betrieb zu stellen und in iobroker den Ems-mode auf "General". Der SOC sollte bei dem Test deutlich grösser, als der min SOC sein, um eine Erhaltungsladung zu vermeiden.

                  BertDerKleineB 1 Reply Last reply
                  0
                  • B besimo

                    @bertderkleine
                    Ich glaube, da hat chatgpt wieder Unwahrheiten geschrieben. Die Option Langzeitspeicherung scheint es wohl doch nicht zu geben. Die Erhaltungsladung kann bei Erreichen min SOC nur Manuell durch Ziehen des Schukostecker vermieden werden.
                    Evtl. gäbe es noch die Option über den Zeitplan in der App auf kein Betrieb zu stellen und in iobroker den Ems-mode auf "General". Der SOC sollte bei dem Test deutlich grösser, als der min SOC sein, um eine Erhaltungsladung zu vermeiden.

                    BertDerKleineB Offline
                    BertDerKleineB Offline
                    BertDerKleine
                    wrote on last edited by
                    #9

                    @besimo
                    Ich verstehe noch nicht, was Du erreichen willst.
                    Wenn Du den Speicher nutzen willst, muss er voll im betrieb sein. Ob er jetzt über die App/Cloud gesteuert wird oder per MQTT dürfte keinen fundamentalen Unterscheid machen. Er muss ganz normal am Netz hängen und dann kostet die Erhaltungsladung Strom.

                    Wenn ihn abklemmst gibts natürlich keine Erhaltungsladung, aber das Ding bringt Dir natürlich so nix.

                    Statt Abklemmen kannst Du auch den Kopf 8 Sekunden drücken und den Speicher in den Inselmodus (off grid) versetzen. Dann ist auch Ruhe.

                    Ich kann mir aber keinen vernünftigen Fall vorstellen, wo tägliches oder gar stündliches An/Abklemmen irgendeinen wirtschaftlichen Sinn macht.

                    Ich schau die Tage mal, was ich über Stromverbrauch zur Erhaltung finden kann. Dazu brauche ich wieder Sonne und einen vollen Speicher.

                    Hoymiles schreibt in der BDA übrigens:

                    "• Die optimale Lebensdauer des Akkus wird durch regelmäßiges Aufladen erreicht. Laden Sie den Akku
                    stets alle paar Monate auf, selbst wenn er nicht verwendet wird.

                    • Wenn Sie den Akku über einen längeren Zeitraum einlagern, laden Sie das MS-A2 vor der Lagerung bei Raumtemperatur auf 40 % bis 50 % auf. "

                    S B 2 Replies Last reply
                    0
                    • BertDerKleineB BertDerKleine

                      @besimo
                      Ich verstehe noch nicht, was Du erreichen willst.
                      Wenn Du den Speicher nutzen willst, muss er voll im betrieb sein. Ob er jetzt über die App/Cloud gesteuert wird oder per MQTT dürfte keinen fundamentalen Unterscheid machen. Er muss ganz normal am Netz hängen und dann kostet die Erhaltungsladung Strom.

                      Wenn ihn abklemmst gibts natürlich keine Erhaltungsladung, aber das Ding bringt Dir natürlich so nix.

                      Statt Abklemmen kannst Du auch den Kopf 8 Sekunden drücken und den Speicher in den Inselmodus (off grid) versetzen. Dann ist auch Ruhe.

                      Ich kann mir aber keinen vernünftigen Fall vorstellen, wo tägliches oder gar stündliches An/Abklemmen irgendeinen wirtschaftlichen Sinn macht.

                      Ich schau die Tage mal, was ich über Stromverbrauch zur Erhaltung finden kann. Dazu brauche ich wieder Sonne und einen vollen Speicher.

                      Hoymiles schreibt in der BDA übrigens:

                      "• Die optimale Lebensdauer des Akkus wird durch regelmäßiges Aufladen erreicht. Laden Sie den Akku
                      stets alle paar Monate auf, selbst wenn er nicht verwendet wird.

                      • Wenn Sie den Akku über einen längeren Zeitraum einlagern, laden Sie das MS-A2 vor der Lagerung bei Raumtemperatur auf 40 % bis 50 % auf. "

                      S Offline
                      S Offline
                      spiffel1234
                      wrote on last edited by spiffel1234
                      #10

                      Hi ich finde es jetzt schon mal super das das Hoymiles MSA 2 dann jetzt auch mqtt kann. Was kann es alles für Werte liefern auch wenn evtl. nur Read Only. Evtl gibt es ja dann mal einen Adapter wo man Einstellungen vornehmen kann... so hat man halt die Datenpunkte einzeln.

                      BertDerKleineB 1 Reply Last reply
                      0
                      • BertDerKleineB BertDerKleine

                        @besimo
                        Ich verstehe noch nicht, was Du erreichen willst.
                        Wenn Du den Speicher nutzen willst, muss er voll im betrieb sein. Ob er jetzt über die App/Cloud gesteuert wird oder per MQTT dürfte keinen fundamentalen Unterscheid machen. Er muss ganz normal am Netz hängen und dann kostet die Erhaltungsladung Strom.

                        Wenn ihn abklemmst gibts natürlich keine Erhaltungsladung, aber das Ding bringt Dir natürlich so nix.

                        Statt Abklemmen kannst Du auch den Kopf 8 Sekunden drücken und den Speicher in den Inselmodus (off grid) versetzen. Dann ist auch Ruhe.

                        Ich kann mir aber keinen vernünftigen Fall vorstellen, wo tägliches oder gar stündliches An/Abklemmen irgendeinen wirtschaftlichen Sinn macht.

                        Ich schau die Tage mal, was ich über Stromverbrauch zur Erhaltung finden kann. Dazu brauche ich wieder Sonne und einen vollen Speicher.

                        Hoymiles schreibt in der BDA übrigens:

                        "• Die optimale Lebensdauer des Akkus wird durch regelmäßiges Aufladen erreicht. Laden Sie den Akku
                        stets alle paar Monate auf, selbst wenn er nicht verwendet wird.

                        • Wenn Sie den Akku über einen längeren Zeitraum einlagern, laden Sie das MS-A2 vor der Lagerung bei Raumtemperatur auf 40 % bis 50 % auf. "

                        B Offline
                        B Offline
                        besimo
                        wrote on last edited by
                        #11

                        @bertderkleine
                        Da der Akku sehr klein ist, würde bei mir die tägliche Nutzungszeit sehr kurz sein, etwa 2 Stunden Laden am Tag und 6-8 Stunden Entladen in der Nacht, also i.M. 15 Stunden möglicher Standby pro Tag. Wäre blöd, wenn er in dieser Zeit wegen dem MQtt-Modus im vollen Betrieb bleibt. Wenn er da z.B. 33 W brauchen würde, also 30 W mehr, als im echten Standby, wären das 30x15x365=164kWh Jahres-Mehrverbrauch, mehr als mein Gefrierschrank.

                        Und generell will ich am Ende alles in ioBroker automatisiert steuern, am besten per javascript, so dass ich die Abfrageintervalle über den Tagesverkauf variabel gestalten kann.
                        Die manuelle Geschichte war ja nur zu Testzwecken gedacht, um rauszubekommen, wie hoch der Eigenverbrauch im "Mqtt-Standby" ist.

                        BertDerKleineB 1 Reply Last reply
                        0
                        • S spiffel1234

                          Hi ich finde es jetzt schon mal super das das Hoymiles MSA 2 dann jetzt auch mqtt kann. Was kann es alles für Werte liefern auch wenn evtl. nur Read Only. Evtl gibt es ja dann mal einen Adapter wo man Einstellungen vornehmen kann... so hat man halt die Datenpunkte einzeln.

                          BertDerKleineB Offline
                          BertDerKleineB Offline
                          BertDerKleine
                          wrote on last edited by
                          #12

                          @spiffel1234 sagte in Erfahrung: Hoymiles MS-A2 Akku via MQTT iobroker steuern:

                          Was kann es alles für Werte liefern auch wenn evtl. nur Read Only.

                          Schau oben in den ersten Link in die Entwicklerdoku. Da sind Kapitel 10 und 11 mit allen ausgegebenen Werten drin. Die landen in zwei JSONs.

                          1 Reply Last reply
                          0
                          • B besimo

                            @bertderkleine
                            Da der Akku sehr klein ist, würde bei mir die tägliche Nutzungszeit sehr kurz sein, etwa 2 Stunden Laden am Tag und 6-8 Stunden Entladen in der Nacht, also i.M. 15 Stunden möglicher Standby pro Tag. Wäre blöd, wenn er in dieser Zeit wegen dem MQtt-Modus im vollen Betrieb bleibt. Wenn er da z.B. 33 W brauchen würde, also 30 W mehr, als im echten Standby, wären das 30x15x365=164kWh Jahres-Mehrverbrauch, mehr als mein Gefrierschrank.

                            Und generell will ich am Ende alles in ioBroker automatisiert steuern, am besten per javascript, so dass ich die Abfrageintervalle über den Tagesverkauf variabel gestalten kann.
                            Die manuelle Geschichte war ja nur zu Testzwecken gedacht, um rauszubekommen, wie hoch der Eigenverbrauch im "Mqtt-Standby" ist.

                            BertDerKleineB Offline
                            BertDerKleineB Offline
                            BertDerKleine
                            wrote on last edited by
                            #13

                            @besimo sagte in Erfahrung: Hoymiles MS-A2 Akku via MQTT iobroker steuern:

                            Wäre blöd, wenn er in dieser Zeit wegen dem MQtt-Modus im vollen Betrieb bleibt.

                            Erstens verstehe ich nicht, warum die annimmst, dass ein "MQTT-Modus" anders Strom verbraucht als ein anderer, wo Cloud und App steuern. Ich denke, die Nutzung des Protokolls hat keine grosse Bedeutung.

                            Zweitens wirst Du im Schnitt (!) länger Aufladen und öfters, wiederholend. Das deutsche Wetter bietet ja üblicherweise was anderes als digital blauen Himmel oder gar keine Sonne. D.h. beim Laden/Entladen wird es oft hin- und hergehen.

                            1 Reply Last reply
                            0
                            • B besimo

                              @bertderkleine
                              Kann man den MS-A2 im Mqtt-Mode in den Standby zwingen z.B. durch Set Power = 0 W ?
                              Hat jemand den Eigenverbrauch gemessen bei Power = 0 W ?

                              BertDerKleineB Offline
                              BertDerKleineB Offline
                              BertDerKleine
                              wrote on last edited by BertDerKleine
                              #14

                              @besimo sagte in Erfahrung: Hoymiles MS-A2 Akku via MQTT iobroker steuern:

                              Hat jemand den Eigenverbrauch gemessen bei Power = 0 W ?

                              Ich habe MQTT aktuell lesend verbunden (d.h. der Akku sendet alle paar Sekunden den Status) und jetzt gerade ist er voll.
                              Der Eigenverbrauch des Akkus in der Gammelphase, den ein zwischengestecktes Strommessgerät anzeigt liegt bei 6,5 W (in dem Fall bei zwei in Reihe geschalteten Akkus).

                              Das sagt natürlich nicht automatisch etwas aus darüber, ob er irgendwann mal etwas nachlädt.

                              PS:
                              Hier https://www.smartzone.de/hoymiles-ms-a2-speicher-test/ ganz unten gibt es auch Angaben zur Effizienz und Eigenverbrauch

                              S 1 Reply Last reply
                              1
                              • BertDerKleineB BertDerKleine

                                @besimo sagte in Erfahrung: Hoymiles MS-A2 Akku via MQTT iobroker steuern:

                                Hat jemand den Eigenverbrauch gemessen bei Power = 0 W ?

                                Ich habe MQTT aktuell lesend verbunden (d.h. der Akku sendet alle paar Sekunden den Status) und jetzt gerade ist er voll.
                                Der Eigenverbrauch des Akkus in der Gammelphase, den ein zwischengestecktes Strommessgerät anzeigt liegt bei 6,5 W (in dem Fall bei zwei in Reihe geschalteten Akkus).

                                Das sagt natürlich nicht automatisch etwas aus darüber, ob er irgendwann mal etwas nachlädt.

                                PS:
                                Hier https://www.smartzone.de/hoymiles-ms-a2-speicher-test/ ganz unten gibt es auch Angaben zur Effizienz und Eigenverbrauch

                                S Offline
                                S Offline
                                spiffel1234
                                wrote on last edited by spiffel1234
                                #15

                                Hi ich muss nochmal einhaken weis jemand ob der MS-A2 Akku auch im "OpenDTU on Battery" supported wird? Hier kann man ja angeblich eine Nulleinspeisung machen. Evtl. Ich stelle jetzt erst mal von OPENDTU auf OPENDTU on Battery um.

                                @BertDerKleine vielen Dank für den Hinweis.

                                1 Reply Last reply
                                0
                                • BertDerKleineB BertDerKleine

                                  Ich schildere hier mal, wie ich den Hoymiles MS-A2 standalone (kein WR, keine Balkonstrom-Panels, kein Shelly, kein Uni-Meter) per MQTT zum laufen bringe - als Nachrüstlösung für eine PV-Anlage.

                                  Vielleicht kann das ja jemand gebrauchen als Idee. Kann man bestimmt alles noch besser machen, aber als Basis kann ich sagen, dass es funktioniert. 😃

                                  Eventuell

                                  Hintergrund:
                                  Speziell für das Szenario, dass der MS-A2 standalone "als große Powerbank" betrieben werden soll, also ohne eingestöpselte Balkonstrom-Panels, braucht es eine Lade/Entladesteuerung, die auf Überschuß und Mangel reagiert.
                                  Seit Anfang Juni 2025 beherrscht der MS-A2 nativ MQTT, was nun eine noch einfachere Lösung ermöglicht, nämlich die direkte Steuerung über iobroker.

                                  Die offizielle Doku zum MQTT Feature findet man hier :

                                  • https://www.photovoltaikforum.com/core/file-download/507290/
                                  • https://www.photovoltaikforum.com/core/file-download/507291/
                                  • Der User "Rong" in dem Thread ist übrigens anscheinend ein Mitarbeiter des Herstelllers Hoymiles.

                                  Was braucht man also an Zutaten?

                                  • eine laufende iobroker Installation und einen eingestöpselten und mit dem WLAN verbundenen MS-A2, dessen Firmware über die Handy-App aktualisiert wurde.
                                  • einen MQTT Broker Adapter im iobroker
                                  • in der S-Miles App unter Zahnrad / MQTT-Service die Daten konfigurieren (Server IP Adresse des iobrokers, den Port (meist 1883) und Benutzernamen / Passwort.

                                  Dann tauscht der Akku mit iobroker Daten aus und im iobroker Objekt-Baum findet man unter mqtt / 0 / homeassistant nun die Datenpunkte.

                                  Standardmäßig lassen sich die Datenpunkte nur auslesen, aber nichts steuern. Um den Akku steuern zu können, muss man in mqtt.0.homeassistant.select.MSA-123456789.ems_mode.command manuell den Befehl mqtt_ctrl einmalig absetzen (der bleibt dann da).

                                  Die Steuerbefehle (Angaben in Watt) zum Laden/Entladen setzt man später in mqtt.0.homeassistant.number.MSA-123456789.power_ctrl.set ab. Dort stehen positive Werte für Entladen und negative für Laden des Akkus.

                                  Genutzte Aliase:

                                  Mein Steuerungsskript basiert auf ein paar Alias-Datenpunkten:

                                  • alias.0.Akku_befohlene_Entladeleistung --> mqtt.0.homeassistant.number.MSA-123456789.power_ctrl.set
                                  • alias.0.Akku_Ladezustand --> mqtt.0.homeassistant.sensor.MSA-123456789.quick.state mit Alias-Konverter beim Lesen JSON.parse(val).soc
                                  • alias.0.Akku_grid_on_power --> mqtt.0.homeassistant.sensor.MSA-123456789.quick.state mit Alias-Konverter beim Lesen JSON.parse(val).grid_on_p

                                  Mehr braucht es nicht. Man kann deutlich mehr Infos auslesen, aber zur Steuerung reicht das.

                                  Benötigte Datenquelle:

                                  Natürlich braucht es zusätzlich einen Datenpunkt im iobroker, der den aktuell vorliegenden Stromüberschuss im Haus liefert.

                                  In meinem Skript wird aktuell alias.0.Stromüberschuss_IR-Lesekopf verwendet, was den Überschuss direkt vom offiziellen Stromzähler ausliest (mittels eines Tasmota-basierten Infrarot Lesekopfs, den man sich leicht für kleines Geld beschaffen kann und der ebenfalls seine daten via  MQTT zu iobroker sendet). Diese Daten können aber auch von einem PV-Wechselrichter oder Stromzähler kommen, Hauptsache es ist der Stromüberschuss (positive Watt-Werte für einen Überschuss, d.h. Strom der sonst ins öffentliche Netz eingespeist wird und negative Werte für Netzbezug).

                                  Begrenzungen der Leistung und Entladung via App:

                                  Egal, was Ihr per MQTT an Befehlen sendet, die Lade- und Entladeleistung des Akkus hält sich an die Vorgaben, die Ihr via der S-Miles App eingegeben habt unter

                                  • Zahnrad / Einstellung der Ausgangsleistung (meist 800 W)
                                  • Zahnrad / Netzgebundene Eingangseinstellung (meist 800 W)
                                  • Zahnrad / Betriebsmodus / Eigenverbrauch / Entladeschlussstufe (meist 10%)

                                  Das ist im Skript alles auch mit abgefangen, aber m.W. passt der Akku da auch drauf auf.

                                  Besonderheit: Vergesslichkeit nach 59 Sekunden & Ignoranz bei immer gleichen Zahlen

                                  Es gibt eine Besonderheit, die zu beachten ist, nämlich vergisst der Akku nach 59 Sekunden den per MQTT vorgegebenen Befehl zum Laden/Entladen wieder. Und außerdem ignoriert er auch simple Wiederholungen einer Vorgabe der exakt selben Wattzahl.

                                  Sobald er diese Eure Vorgabe vergessen hat, führt der Akku das aus, was unter Zahnrad / Hauslasteinstellungen / Lastleistungskurve / Kurve 1 / (alle Tage und Uhrzeiten) in der App hinterlegt ist. Falls da also z.B. 100 W steht, wird der Akku dann mit 100 W entladen. Man kann in der App hier auch einfach "0 W" hinterlegen, so dass der Akku dann nix macht.

                                  Aus diesem Grund sendet mein Skript alle 30 Sekunden eine neue Wattvorgabe und zusätzlich alterniert diese immer um 0,1 Watt. So wird die Steuerung aufrechterhalten.

                                  Skript:

                                  Und hier findet Ihr mein Javascript als Denkanstoß.
                                  Ein großer Teil des Skripts ist Doku, Logging und Fehlerfindung gewidmet, man kann es also auch kürzen.

                                  /**
                                   * AKKU-MANAGEMENT-SKRIPT FÜR IO BROKER
                                   * 
                                   * Dieses Skript steuert einen Batteriespeicher basierend auf dem aktuellen Stromüberschuss.
                                   * Es lädt den Akku bei PV-Überschuss und entlädt ihn bei Strombedarf.
                                   * Entwurf von BertDerKleine
                                   * 
                                   * Merkmale:
                                   * - Kontinuierliche Regelung unabhängig von Tageszeit
                                   * - Getrennte Zielfaktoren für Lade- und Entladevorgänge
                                   * - PI-Regler für stabile Regelung
                                   * - SOC-Schutz gegen Überladung und Tiefentladung
                                   * - Alternierende Steuerbefehle für zuverlässige Akku-Kommunikation
                                   * - Ausführliche Diagnosefunktionen
                                   * - Konfigurierbares Logging
                                   */
                                  
                                  // ========== KONFIGURATION ==========
                                  const AUSFUEHRLICHES_LOGGING = false;  // Auf TRUE setzen für detaillierte Regel-Logs (nur für Debugging)
                                  const MAX_LOG_LAENGE = 100;             // Maximale Anzahl gespeicherter Log-Einträge für Diagnose
                                  
                                  // ========== GLOBALE VARIABLEN ==========
                                  /**
                                   * Intervall für das Senden von Steuerbefehlen (in Millisekunden)
                                   * Der Akku benötigt regelmäßige Aktualisierungen, da er Befehle nach 60-90 Sekunden "vergisst"
                                   * 30000 = 30 Sekunden ist ein bewährter Wert für zuverlässige Kommunikation
                                   */
                                  const alternierIntervall = 30000;
                                  
                                  /**
                                   * Datenpunkt für Akku-Steuerbefehle
                                   * - Positive Werte: Entladen (Leistungsabgabe)
                                   * - Negative Werte: Laden (Leistungsaufnahme)
                                   */
                                  const AKTIONS_DATENPUNKT = 'alias.0.Akku_befohlene_Entladeleistung';
                                  
                                  /** Datenpunkt für die aktuell gemessene Akku-Leistung */
                                  const AKTUELLE_AKKULEISTUNG = 'alias.0.Akku_grid_on_power';
                                  
                                  // Betriebsstatus
                                  let regelungAktiv = false;  // Gibt an, ob die Regelung aktuell läuft
                                  
                                  // Intervalle
                                  let messIntervall = null;    // Intervall für Leistungsmessungen
                                  let regelIntervall = null;   // Intervall für Regelberechnungen
                                  let sendeIntervall = null;   // Intervall für Steuerbefehle
                                  
                                  // Regelvariablen
                                  let mittelungArray = [];     // Speichert die letzten Messwerte für die Mittelwertbildung
                                  let aktuellerSollwert = 0;   // Aktuell berechneter Sollwert für Akku-Leistung
                                  let toggleFlag = true;       // Steuert die Alternierung der Steuerbefehle (+0.1W Wechsel)
                                  let integral = 0;            // Integralanteil des PI-Reglers
                                  
                                  // ========== PARAMETER ==========
                                  /**
                                   * Maximale Ladeleistung in Watt
                                   * Sollte unterhalb der technischen Grenzen des Akkus liegen.
                                   */
                                  const maxLadeleistung = 800;
                                  
                                  /**
                                   * Maximale Entladeleistung in Watt
                                   * Abhängig von Akku-Kapazität und Wechselrichter.
                                   */
                                  const maxEntladeleistung = 800;
                                  
                                  /**
                                   * Intervall für Leistungsmessungen in Millisekunden
                                   * 10000 = 10 Sekunden ist ein guter Kompromiss zwischen Aktualität und Stabilität
                                   */
                                  const samplingIntervall = 10000;
                                  
                                  /**
                                   * Faktor für den Integralanteil des PI-Reglers
                                   * Ein höherer Wert reagiert stärker auf anhaltende Abweichungen
                                   * 0.2 ist ein moderater Wert für stabile Regelung ohne zu starkes Überschwingen
                                   */
                                  const integralFaktor = 0.2;
                                  
                                  /** Ladezustand bei dem das Laden gestoppt wird (Vermeidung von Überladung) */
                                  const SOC_LADESTOPP = 98;
                                  
                                  /** Ladezustand bei dem das Entladen gestoppt wird (Vermeidung von Tiefentladung) */
                                  const SOC_ENTLADESTOPP = 10;
                                  
                                  /**
                                   * Zielfaktoren für Regelung
                                   * 
                                   * zielFaktorLaden: Anteil des Überschusses der fürs Laden genutzt wird (0-1)
                                   *   - 0.8 = 80% des Überschusses werden zum Laden genutzt (konservativ)
                                   *   - Höhere Werte nutzen mehr Überschuss, können aber zu Netzinstabilität führen
                                   * 
                                   * zielFaktorEntladen: Anteil des Bedarfs der durch Entladen gedeckt wird (0-1)
                                   *   - 1.0 = 100% des Bedarfs werden durch Entladen gedeckt (maximale Autarkie)
                                   *   - Niedrigere Werte schonen den Akku, erhöhen aber Netzbezug
                                   */
                                  const zielFaktorLaden = 0.8;
                                  const zielFaktorEntladen = 1.0;
                                  
                                  // ========== LOGGING SYSTEM ==========
                                  let logHistorie = [];  // Speichert die letzten Log-Einträge für Diagnosezwecke
                                  
                                  /**
                                   * Verbesserte Log-Funktion mit Historie-Speicherung und Level-Steuerung
                                   * 
                                   * @param {string} nachricht - Die zu loggende Nachricht
                                   * @param {string} level - Log-Level ('debug', 'info', 'warn', 'error')
                                   */
                                  function log(nachricht, level = 'info') {
                                      // Erstelle formatierten Log-Eintrag
                                      const timestamp = new Date().toISOString();
                                      const logEintrag = `[${timestamp}] [${level.toUpperCase()}] ${nachricht}`;
                                      
                                      // Füge zur Historie hinzu (begrenzt auf MAX_LOG_LAENGE)
                                      logHistorie.push(logEintrag);
                                      if (logHistorie.length > MAX_LOG_LAENGE) {
                                          logHistorie.shift();
                                      }
                                      
                                      // Ausgabe basierend auf Level und Konfiguration
                                      switch(level) {
                                          case 'error':
                                              console.error(logEintrag);
                                              break;
                                          case 'warn':
                                              console.warn(logEintrag);
                                              break;
                                          case 'info':
                                              console.log(logEintrag);
                                              break;
                                          case 'debug':
                                              // Debug-Logs nur bei aktiviertem ausführlichen Logging
                                              if (AUSFUEHRLICHES_LOGGING) {
                                                  console.log(logEintrag);
                                              }
                                              break;
                                          default:
                                              console.log(logEintrag);
                                      }
                                  }
                                  
                                  // ========== HELFERFUNKTIONEN ==========
                                  /**
                                   * Setzt den Steuerungsdatenpunkt mit Fehlerbehandlung
                                   * 
                                   * @param {number} wert - Der zu setzende Wert
                                   * @returns {boolean} True bei Erfolg, False bei Fehler
                                   */
                                  function sicherSetState(wert) {
                                      try {
                                          setState(AKTIONS_DATENPUNKT, wert, false);
                                          return true;
                                      } catch (e) {
                                          log(`Fehler beim Setzen des Datenpunkts: ${e.message}`, 'error');
                                          return false;
                                      }
                                  }
                                  
                                  /**
                                   * Bereinigt den gemessenen Überschuss um den aktuellen Akku-Beitrag
                                   * 
                                   * @param {number} ueberschussGemessen - Gemessener Stromüberschuss
                                   * @param {number} akkuLeistung - Aktuelle Akku-Leistung (positiv = Entladen, negativ = Laden)
                                   * @returns {number} Bereinigter Überschuss
                                   */
                                  function bereinigeLeistung(ueberschussGemessen, akkuLeistung) {
                                      return ueberschussGemessen - akkuLeistung;
                                  }
                                  
                                  // ========== FLEXIBLE REGELUNG ==========
                                  /**
                                   * Startet die kontinuierliche Akku-Regelung
                                   */
                                  function starteFlexibleRegelung() {
                                      if (regelungAktiv) {
                                          log("Regelung bereits aktiv - Start abgebrochen", 'debug');
                                          return;
                                      }
                                      
                                      stoppeRegelung();
                                      log("Starte flexible Regelung...");
                                      
                                      regelungAktiv = true;
                                      mittelungArray = [];
                                      aktuellerSollwert = 0;
                                      integral = 0;
                                      toggleFlag = true;
                                  
                                      // Initialer Status-Log mit Konfiguration
                                      log(`Regelung gestartet mit:
                                    Max Laden: ${maxLadeleistung}W
                                    Max Entladen: ${maxEntladeleistung}W
                                    Laden-Zielfaktor: ${zielFaktorLaden}
                                    Entladen-Zielfaktor: ${zielFaktorEntladen}
                                    SOC-Limits: Ladestopp >${SOC_LADESTOPP}%, Entladestopp <${SOC_ENTLADESTOPP}%`, 'info');
                                  
                                      // Messintervall - Sammelt regelmäßig Leistungsdaten
                                      messIntervall = setInterval(() => {
                                          try {
                                              const ueberschussGemessen = getState('alias.0.Stromüberschuss_IR-Lesekopf').val;
                                              const akkuLeistung = getState(AKTUELLE_AKKULEISTUNG).val;
                                              const bereinigteLeistung = bereinigeLeistung(ueberschussGemessen, akkuLeistung);
                                              
                                              mittelungArray.push(bereinigteLeistung);
                                              if (mittelungArray.length > 6) mittelungArray.shift();
                                          } catch (e) {
                                              log("Messfehler: " + e.message, 'error');
                                          }
                                      }, samplingIntervall);
                                  
                                      // Regelintervall - Berechnet alle 20 Sekunden neue Sollwerte
                                      regelIntervall = setInterval(() => {
                                          try {
                                              if (mittelungArray.length < 3) {
                                                  log("Nicht genug Messwerte für Regelung", 'debug');
                                                  return;
                                              }
                                              
                                              // Berechne gleitenden Mittelwert der letzten Messungen
                                              const summe = mittelungArray.reduce((a, b) => a + b, 0);
                                              const mittelwert = summe / mittelungArray.length;
                                              
                                              // Bestimmung des Betriebsmodus
                                              const istLadebetrieb = mittelwert > 0; // Positiver Wert = Überschuss = Laden
                                              const zielFaktor = istLadebetrieb ? zielFaktorLaden : zielFaktorEntladen;
                                              
                                              // Berechnung der benötigten Akku-Leistung
                                              const zielLeistung = Math.abs(mittelwert) * zielFaktor;
                                              const zielwert = istLadebetrieb ? -zielLeistung : zielLeistung;
                                              
                                              // PI-Regler berechnet Anpassung
                                              const differenz = zielwert - aktuellerSollwert;
                                              integral += differenz * integralFaktor;
                                              
                                              // Integrator-Begrenzung
                                              const integratorLimit = istLadebetrieb ? maxLadeleistung : maxEntladeleistung;
                                              integral = Math.min(Math.max(integral, -integratorLimit), integratorLimit);
                                              
                                              // Neuen Sollwert berechnen
                                              let neuerSollwert = aktuellerSollwert + differenz + integral;
                                              
                                              // Physikalische Grenzen einhalten
                                              if (istLadebetrieb) {
                                                  neuerSollwert = Math.min(neuerSollwert, 0); // Max 0 = keine Entladung
                                                  neuerSollwert = Math.max(neuerSollwert, -maxLadeleistung); // Min = -maxLadeleistung
                                              } else {
                                                  neuerSollwert = Math.max(neuerSollwert, 0); // Min 0 = kein Laden
                                                  neuerSollwert = Math.min(neuerSollwert, maxEntladeleistung); // Max = maxEntladeleistung
                                              }
                                              
                                              // Sanfte Anpassung (30% pro Schritt)
                                              const aenderung = neuerSollwert - aktuellerSollwert;
                                              if (Math.abs(aenderung) > 10) {
                                                  aktuellerSollwert += aenderung * 0.3;
                                              } else {
                                                  aktuellerSollwert = neuerSollwert;
                                              }
                                              
                                              // Ausführliches Logging nur bei Bedarf
                                              if (AUSFUEHRLICHES_LOGGING) {
                                                  log(`Regelberechnung:
                                    Überschuss (gemittelt): ${mittelwert.toFixed(1)}W
                                    Ziel-Leistung: ${zielLeistung.toFixed(1)}W
                                    Zielwert (Akku): ${zielwert.toFixed(1)}W
                                    Neuer Sollwert: ${neuerSollwert.toFixed(1)}W
                                    Aktueller Sollwert: ${aktuellerSollwert.toFixed(1)}W
                                    Differenz: ${differenz.toFixed(1)}W
                                    Integral: ${integral.toFixed(1)}
                                    Faktor: ${zielFaktor}
                                    Modus: ${istLadebetrieb ? 'Laden' : 'Entladen'}`, 'debug');
                                              }
                                          } catch (e) {
                                              log("Regelfehler: " + e.message, 'error');
                                          }
                                      }, 20000);
                                  
                                      // Sendeintervall - Sendet alle 30 Sekunden Steuerbefehle
                                      sendeIntervall = setInterval(() => {
                                          if (!regelungAktiv) return;
                                          
                                          // SOC-Schutz - Verhindert Überladung und Tiefentladung
                                          const akkuSOC = getState('alias.0.Akku_Ladezustand').val;
                                          let effektiverSollwert = aktuellerSollwert;
                                          
                                          if (effektiverSollwert < 0 && akkuSOC > SOC_LADESTOPP) {
                                              effektiverSollwert = 0;
                                              log(`LADESTOPP: Akku > ${SOC_LADESTOPP}% (SOC: ${akkuSOC.toFixed(1)}%)`, 'warn');
                                          }
                                          
                                          if (effektiverSollwert > 0 && akkuSOC < SOC_ENTLADESTOPP) {
                                              effektiverSollwert = 0;
                                              log(`ENTLADESTOPP: Akku < ${SOC_ENTLADESTOPP}% (SOC: ${akkuSOC.toFixed(1)}%)`, 'warn');
                                          }
                                          
                                          // Alternierende Werte senden (jeder 2. Befehl +0.1W)
                                          const wert = toggleFlag ? effektiverSollwert : effektiverSollwert + 0.1;
                                          sicherSetState(wert);
                                          toggleFlag = !toggleFlag;
                                          
                                          // Protokolliere nur die ersten 20 Minuten oder bei Moduswechsel
                                          log(`Steuerbefehl gesendet: ${wert.toFixed(1)}W (${toggleFlag ? 'nächstes Mal Basiswert' : 'nächstes Mal +0.1W'})`, 'debug');
                                      }, alternierIntervall);
                                  
                                      log("Flexible Regelung erfolgreich gestartet", 'info');
                                  }
                                  
                                  /**
                                   * Stoppt die laufende Regelung
                                   */
                                  function stoppeRegelung() {
                                      if (!regelungAktiv) return;
                                      
                                      clearInterval(messIntervall);
                                      clearInterval(regelIntervall);
                                      clearInterval(sendeIntervall);
                                      regelungAktiv = false;
                                      
                                      sicherSetState(0);
                                      log("Regelung gestoppt", 'info');
                                  }
                                  
                                  // ========== HAUPTPROGRAMM ==========
                                  /**
                                   * Initialisiert das Skript
                                   */
                                  function initSkript() {
                                      try {
                                          starteFlexibleRegelung();
                                          log("Skript initialisierung abgeschlossen");
                                          
                                          // Aktiviere temporäres ausführliches Logging für die ersten 10 Minuten
                                          setTimeout(() => {
                                              if (!AUSFUEHRLICHES_LOGGING) {
                                                  log("Initiale Debug-Periode beendet - Detaillogs deaktiviert");
                                              }
                                          }, 600000); // 10 Minuten
                                      } catch (e) {
                                          log("Initialisierungsfehler: " + e.message, 'error');
                                      }
                                  }
                                  
                                  // Startverzögerung für Systeminitialisierung
                                  setTimeout(initSkript, 5000);
                                  
                                  // ========== EVENT-HANDLER ==========
                                  // Überwacht Ladezustandsänderungen
                                  on({ id: 'alias.0.Akku_Ladezustand', change: 'ne' }, (state) => {
                                      const soc = state.val;
                                      
                                      // Protokolliere nur bei relevanten Änderungen
                                      if (soc > SOC_LADESTOPP && aktuellerSollwert < 0) {
                                          log(`AKTION: Ladung pausiert (SOC: ${soc}% > ${SOC_LADESTOPP}%)`, 'warn');
                                      } else if (soc < SOC_ENTLADESTOPP && aktuellerSollwert > 0) {
                                          log(`AKTION: Entladung pausiert (SOC: ${soc}% < ${SOC_ENTLADESTOPP}%)`, 'warn');
                                      } else if (soc <= SOC_LADESTOPP && soc >= SOC_ENTLADESTOPP) {
                                          log(`SOC im normalen Bereich: ${soc}%`, 'debug');
                                      }
                                  });
                                  
                                  // ========== DIAGNOSE & MONITORING ==========
                                  // Regelmäßige Systemdiagnose
                                  const diagIntervall = setInterval(() => {
                                      const ueberschuss = getState('alias.0.Stromüberschuss_IR-Lesekopf').val || 0;
                                      const akkuLeistung = getState(AKTUELLE_AKKULEISTUNG).val || 0;
                                      const akkuSOC = getState('alias.0.Akku_Ladezustand').val || 0;
                                      const istLadebetrieb = aktuellerSollwert < 0;
                                      
                                      log(`Systemdiagnose:
                                    Regelstatus: ${regelungAktiv ? "AKTIV" : "INAKTIV"}
                                    Betriebsmodus: ${istLadebetrieb ? 'LADEN' : 'ENTLADEN'}
                                    Sollleistung: ${aktuellerSollwert.toFixed(1)}W
                                    Istleistung: ${akkuLeistung}W
                                    Überschuss: ${ueberschuss}W
                                    SOC: ${akkuSOC.toFixed(1)}%
                                    Messwerte: ${mittelungArray.length} gespeichert
                                    Letzte Logs: ${logHistorie.slice(-3).join('\n  ')}`, 
                                    'info');
                                  }, 300000); // Alle 5 Minuten
                                  
                                  // Datenpunkt-Prüfung
                                  function pruefeDatenpunkte() {
                                      const datenpunkte = [
                                          'alias.0.Stromüberschuss_IR-Lesekopf',
                                          'alias.0.Akku_Ladezustand',
                                          AKTUELLE_AKKULEISTUNG,
                                          AKTIONS_DATENPUNKT
                                      ];
                                      
                                      let fehler = false;
                                      datenpunkte.forEach(id => {
                                          if (getState(id) === undefined) {
                                              fehler = true;
                                              log(`KRITISCH: Datenpunkt ${id} nicht gefunden!`, 'error');
                                          }
                                      });
                                      
                                      if (fehler) {
                                          log("Fehlende Datenpunkte - Skriptfunktionalität eingeschränkt", 'error');
                                      } else {
                                          log("Alle benötigten Datenpunkte verfügbar", 'debug');
                                      }
                                  }
                                  
                                  // Verzögerte Datenpunktprüfung
                                  setTimeout(pruefeDatenpunkte, 15000);
                                  
                                  // ========== NOTFALL-SYSTEM ==========
                                  // Automatischer Neustart bei Inaktivität
                                  const watchdogIntervall = setInterval(() => {
                                      if (!regelungAktiv) {
                                          log("Watchdog: Regelung inaktiv - Neustart wird versucht", 'warn');
                                          initSkript();
                                      }
                                  }, 600000); // Prüft alle 10 Minuten
                                  
                                  H Offline
                                  H Offline
                                  hschief
                                  wrote on last edited by
                                  #16

                                  @bertderkleine danke für dein tolles Script, ich werde dies mal ausprobieren und im nächsten Schritt erweitern. Ich bin zwar weit nicht so gut in der Programmierung, aber wird schon klappen.
                                  Da ich einen dynamischen Tarif habe, würde ich folgende Funktion noch reinbringen.

                                  1. Entladung zu einem Zeitpunk wo der Bezugspreis am maximal ist (meist Abends & Morgens)
                                  2. Netzladung bei niedrigen Preisen

                                  Ich kann ja berichten, wird aber ein paar Tage dauern.
                                  Liebe Grüße
                                  Helmut

                                  BertDerKleineB 1 Reply Last reply
                                  0
                                  • H hschief

                                    @bertderkleine danke für dein tolles Script, ich werde dies mal ausprobieren und im nächsten Schritt erweitern. Ich bin zwar weit nicht so gut in der Programmierung, aber wird schon klappen.
                                    Da ich einen dynamischen Tarif habe, würde ich folgende Funktion noch reinbringen.

                                    1. Entladung zu einem Zeitpunk wo der Bezugspreis am maximal ist (meist Abends & Morgens)
                                    2. Netzladung bei niedrigen Preisen

                                    Ich kann ja berichten, wird aber ein paar Tage dauern.
                                    Liebe Grüße
                                    Helmut

                                    BertDerKleineB Offline
                                    BertDerKleineB Offline
                                    BertDerKleine
                                    wrote on last edited by
                                    #17

                                    @hschief sagte in Erfahrung: Hoymiles MS-A2 Akku via MQTT iobroker steuern:

                                    Da ich einen dynamischen Tarif habe, würde ich folgende Funktion noch reinbringen

                                    Wenn Du das zusätzlich zur bisherigen Steuerung nach Überschüssen einbringen willst, bin ich mal gespannt auf die Logik.
                                    Ich denke nämlich, dass eine Programmierung hier der leichtere Teil ist im Vergleich dazu, eine wünschenswerte Logik zu ersinnen.
                                    Einzeln eine Überschusssteuerung geht und einzeln eine Steuerung nach Preisen auch, aber das miteinander zu vermengen - schwierig.

                                    Beispiel: Willst Du wirklich den Rest im Akku morgens entladen, wegen hoher Preise, obwohl vielleicht (!) nachmittags dicke Wolken aufziehen werden und Du dann den Strom selber brauchst?
                                    Natürlich kann man hier centgenaue Arbitragen machen auf Basis von statistischer Erwartung der Sonneneinstrahlung, aber das wird dann schon sehr wissenschaftlich und umfangreich.

                                    H 1 Reply Last reply
                                    0
                                    • BertDerKleineB BertDerKleine

                                      @hschief sagte in Erfahrung: Hoymiles MS-A2 Akku via MQTT iobroker steuern:

                                      Da ich einen dynamischen Tarif habe, würde ich folgende Funktion noch reinbringen

                                      Wenn Du das zusätzlich zur bisherigen Steuerung nach Überschüssen einbringen willst, bin ich mal gespannt auf die Logik.
                                      Ich denke nämlich, dass eine Programmierung hier der leichtere Teil ist im Vergleich dazu, eine wünschenswerte Logik zu ersinnen.
                                      Einzeln eine Überschusssteuerung geht und einzeln eine Steuerung nach Preisen auch, aber das miteinander zu vermengen - schwierig.

                                      Beispiel: Willst Du wirklich den Rest im Akku morgens entladen, wegen hoher Preise, obwohl vielleicht (!) nachmittags dicke Wolken aufziehen werden und Du dann den Strom selber brauchst?
                                      Natürlich kann man hier centgenaue Arbitragen machen auf Basis von statistischer Erwartung der Sonneneinstrahlung, aber das wird dann schon sehr wissenschaftlich und umfangreich.

                                      H Offline
                                      H Offline
                                      hschief
                                      wrote on last edited by hschief
                                      #18

                                      @bertderkleine
                                      Ja, stimmt, ich tast mich mal langsam ran und vielleicht hilft auch der Mittelweg. Erstmal habe ich dein Script heute zum laufen gebracht. Ich habe die Logik zwar noch nicht ganz verstande, aber ich taste mich mal ran.
                                      Folgende Änderungen habe ich mal reingebracht:

                                      1. Zwei weitere Datenpunkte:
                                        const NetzLaden = '0_userdata.0.Technik.Strom.Speicher.MSA-280024340941.NetzLadung';
                                        const Entladen = '0_userdata.0.Technik.Strom.Speicher.MSA-280024340941.Entladung';

                                      Wenn der erste auf True ist, dann wird zwangsweise vom Netz geladen
                                      Wenn der zweite auf True ist, dann wird der Akku entladen. Mit diesem kann ich in einem ersten Schritt das Entladen am Tage blockieren und die Entladung zu einem späteren Zeitpunkt starten (Wenn der Preis oben ist)

                                      In einem anderen Script habe ich nun zB. die Idee, die Tibberpreiskurve auszuwerten und zB. 2h vor dem max Preis && nach 18:00Uhr die Entladung zu starten. In der Regel ist der Akku dann durch den Verbrauch am Abend bis 24:00 Uhr leer.

                                      Den Code habe ich wie folgt angepasst. Wenn du magst, kannst du das natürlich übernehmen oder an eine bessere Stelle überführen.

                                      Zusätzlich habe ich was eingebaut, dass der Ladestrom reduziert wird, wenn die obere Ladegrenze erreicht ist. Ich meine, dies beobachtet zu haben, dass Hoymiles selber reduziert wenn es an die obere Grenze geht.

                                      Ich berichte von meinen weiteren Versuchen 🙂

                                      1 Reply Last reply
                                      1
                                      • BertDerKleineB BertDerKleine

                                        Ich schildere hier mal, wie ich den Hoymiles MS-A2 standalone (kein WR, keine Balkonstrom-Panels, kein Shelly, kein Uni-Meter) per MQTT zum laufen bringe - als Nachrüstlösung für eine PV-Anlage.

                                        Vielleicht kann das ja jemand gebrauchen als Idee. Kann man bestimmt alles noch besser machen, aber als Basis kann ich sagen, dass es funktioniert. 😃

                                        Eventuell

                                        Hintergrund:
                                        Speziell für das Szenario, dass der MS-A2 standalone "als große Powerbank" betrieben werden soll, also ohne eingestöpselte Balkonstrom-Panels, braucht es eine Lade/Entladesteuerung, die auf Überschuß und Mangel reagiert.
                                        Seit Anfang Juni 2025 beherrscht der MS-A2 nativ MQTT, was nun eine noch einfachere Lösung ermöglicht, nämlich die direkte Steuerung über iobroker.

                                        Die offizielle Doku zum MQTT Feature findet man hier :

                                        • https://www.photovoltaikforum.com/core/file-download/507290/
                                        • https://www.photovoltaikforum.com/core/file-download/507291/
                                        • Der User "Rong" in dem Thread ist übrigens anscheinend ein Mitarbeiter des Herstelllers Hoymiles.

                                        Was braucht man also an Zutaten?

                                        • eine laufende iobroker Installation und einen eingestöpselten und mit dem WLAN verbundenen MS-A2, dessen Firmware über die Handy-App aktualisiert wurde.
                                        • einen MQTT Broker Adapter im iobroker
                                        • in der S-Miles App unter Zahnrad / MQTT-Service die Daten konfigurieren (Server IP Adresse des iobrokers, den Port (meist 1883) und Benutzernamen / Passwort.

                                        Dann tauscht der Akku mit iobroker Daten aus und im iobroker Objekt-Baum findet man unter mqtt / 0 / homeassistant nun die Datenpunkte.

                                        Standardmäßig lassen sich die Datenpunkte nur auslesen, aber nichts steuern. Um den Akku steuern zu können, muss man in mqtt.0.homeassistant.select.MSA-123456789.ems_mode.command manuell den Befehl mqtt_ctrl einmalig absetzen (der bleibt dann da).

                                        Die Steuerbefehle (Angaben in Watt) zum Laden/Entladen setzt man später in mqtt.0.homeassistant.number.MSA-123456789.power_ctrl.set ab. Dort stehen positive Werte für Entladen und negative für Laden des Akkus.

                                        Genutzte Aliase:

                                        Mein Steuerungsskript basiert auf ein paar Alias-Datenpunkten:

                                        • alias.0.Akku_befohlene_Entladeleistung --> mqtt.0.homeassistant.number.MSA-123456789.power_ctrl.set
                                        • alias.0.Akku_Ladezustand --> mqtt.0.homeassistant.sensor.MSA-123456789.quick.state mit Alias-Konverter beim Lesen JSON.parse(val).soc
                                        • alias.0.Akku_grid_on_power --> mqtt.0.homeassistant.sensor.MSA-123456789.quick.state mit Alias-Konverter beim Lesen JSON.parse(val).grid_on_p

                                        Mehr braucht es nicht. Man kann deutlich mehr Infos auslesen, aber zur Steuerung reicht das.

                                        Benötigte Datenquelle:

                                        Natürlich braucht es zusätzlich einen Datenpunkt im iobroker, der den aktuell vorliegenden Stromüberschuss im Haus liefert.

                                        In meinem Skript wird aktuell alias.0.Stromüberschuss_IR-Lesekopf verwendet, was den Überschuss direkt vom offiziellen Stromzähler ausliest (mittels eines Tasmota-basierten Infrarot Lesekopfs, den man sich leicht für kleines Geld beschaffen kann und der ebenfalls seine daten via  MQTT zu iobroker sendet). Diese Daten können aber auch von einem PV-Wechselrichter oder Stromzähler kommen, Hauptsache es ist der Stromüberschuss (positive Watt-Werte für einen Überschuss, d.h. Strom der sonst ins öffentliche Netz eingespeist wird und negative Werte für Netzbezug).

                                        Begrenzungen der Leistung und Entladung via App:

                                        Egal, was Ihr per MQTT an Befehlen sendet, die Lade- und Entladeleistung des Akkus hält sich an die Vorgaben, die Ihr via der S-Miles App eingegeben habt unter

                                        • Zahnrad / Einstellung der Ausgangsleistung (meist 800 W)
                                        • Zahnrad / Netzgebundene Eingangseinstellung (meist 800 W)
                                        • Zahnrad / Betriebsmodus / Eigenverbrauch / Entladeschlussstufe (meist 10%)

                                        Das ist im Skript alles auch mit abgefangen, aber m.W. passt der Akku da auch drauf auf.

                                        Besonderheit: Vergesslichkeit nach 59 Sekunden & Ignoranz bei immer gleichen Zahlen

                                        Es gibt eine Besonderheit, die zu beachten ist, nämlich vergisst der Akku nach 59 Sekunden den per MQTT vorgegebenen Befehl zum Laden/Entladen wieder. Und außerdem ignoriert er auch simple Wiederholungen einer Vorgabe der exakt selben Wattzahl.

                                        Sobald er diese Eure Vorgabe vergessen hat, führt der Akku das aus, was unter Zahnrad / Hauslasteinstellungen / Lastleistungskurve / Kurve 1 / (alle Tage und Uhrzeiten) in der App hinterlegt ist. Falls da also z.B. 100 W steht, wird der Akku dann mit 100 W entladen. Man kann in der App hier auch einfach "0 W" hinterlegen, so dass der Akku dann nix macht.

                                        Aus diesem Grund sendet mein Skript alle 30 Sekunden eine neue Wattvorgabe und zusätzlich alterniert diese immer um 0,1 Watt. So wird die Steuerung aufrechterhalten.

                                        Skript:

                                        Und hier findet Ihr mein Javascript als Denkanstoß.
                                        Ein großer Teil des Skripts ist Doku, Logging und Fehlerfindung gewidmet, man kann es also auch kürzen.

                                        /**
                                         * AKKU-MANAGEMENT-SKRIPT FÜR IO BROKER
                                         * 
                                         * Dieses Skript steuert einen Batteriespeicher basierend auf dem aktuellen Stromüberschuss.
                                         * Es lädt den Akku bei PV-Überschuss und entlädt ihn bei Strombedarf.
                                         * Entwurf von BertDerKleine
                                         * 
                                         * Merkmale:
                                         * - Kontinuierliche Regelung unabhängig von Tageszeit
                                         * - Getrennte Zielfaktoren für Lade- und Entladevorgänge
                                         * - PI-Regler für stabile Regelung
                                         * - SOC-Schutz gegen Überladung und Tiefentladung
                                         * - Alternierende Steuerbefehle für zuverlässige Akku-Kommunikation
                                         * - Ausführliche Diagnosefunktionen
                                         * - Konfigurierbares Logging
                                         */
                                        
                                        // ========== KONFIGURATION ==========
                                        const AUSFUEHRLICHES_LOGGING = false;  // Auf TRUE setzen für detaillierte Regel-Logs (nur für Debugging)
                                        const MAX_LOG_LAENGE = 100;             // Maximale Anzahl gespeicherter Log-Einträge für Diagnose
                                        
                                        // ========== GLOBALE VARIABLEN ==========
                                        /**
                                         * Intervall für das Senden von Steuerbefehlen (in Millisekunden)
                                         * Der Akku benötigt regelmäßige Aktualisierungen, da er Befehle nach 60-90 Sekunden "vergisst"
                                         * 30000 = 30 Sekunden ist ein bewährter Wert für zuverlässige Kommunikation
                                         */
                                        const alternierIntervall = 30000;
                                        
                                        /**
                                         * Datenpunkt für Akku-Steuerbefehle
                                         * - Positive Werte: Entladen (Leistungsabgabe)
                                         * - Negative Werte: Laden (Leistungsaufnahme)
                                         */
                                        const AKTIONS_DATENPUNKT = 'alias.0.Akku_befohlene_Entladeleistung';
                                        
                                        /** Datenpunkt für die aktuell gemessene Akku-Leistung */
                                        const AKTUELLE_AKKULEISTUNG = 'alias.0.Akku_grid_on_power';
                                        
                                        // Betriebsstatus
                                        let regelungAktiv = false;  // Gibt an, ob die Regelung aktuell läuft
                                        
                                        // Intervalle
                                        let messIntervall = null;    // Intervall für Leistungsmessungen
                                        let regelIntervall = null;   // Intervall für Regelberechnungen
                                        let sendeIntervall = null;   // Intervall für Steuerbefehle
                                        
                                        // Regelvariablen
                                        let mittelungArray = [];     // Speichert die letzten Messwerte für die Mittelwertbildung
                                        let aktuellerSollwert = 0;   // Aktuell berechneter Sollwert für Akku-Leistung
                                        let toggleFlag = true;       // Steuert die Alternierung der Steuerbefehle (+0.1W Wechsel)
                                        let integral = 0;            // Integralanteil des PI-Reglers
                                        
                                        // ========== PARAMETER ==========
                                        /**
                                         * Maximale Ladeleistung in Watt
                                         * Sollte unterhalb der technischen Grenzen des Akkus liegen.
                                         */
                                        const maxLadeleistung = 800;
                                        
                                        /**
                                         * Maximale Entladeleistung in Watt
                                         * Abhängig von Akku-Kapazität und Wechselrichter.
                                         */
                                        const maxEntladeleistung = 800;
                                        
                                        /**
                                         * Intervall für Leistungsmessungen in Millisekunden
                                         * 10000 = 10 Sekunden ist ein guter Kompromiss zwischen Aktualität und Stabilität
                                         */
                                        const samplingIntervall = 10000;
                                        
                                        /**
                                         * Faktor für den Integralanteil des PI-Reglers
                                         * Ein höherer Wert reagiert stärker auf anhaltende Abweichungen
                                         * 0.2 ist ein moderater Wert für stabile Regelung ohne zu starkes Überschwingen
                                         */
                                        const integralFaktor = 0.2;
                                        
                                        /** Ladezustand bei dem das Laden gestoppt wird (Vermeidung von Überladung) */
                                        const SOC_LADESTOPP = 98;
                                        
                                        /** Ladezustand bei dem das Entladen gestoppt wird (Vermeidung von Tiefentladung) */
                                        const SOC_ENTLADESTOPP = 10;
                                        
                                        /**
                                         * Zielfaktoren für Regelung
                                         * 
                                         * zielFaktorLaden: Anteil des Überschusses der fürs Laden genutzt wird (0-1)
                                         *   - 0.8 = 80% des Überschusses werden zum Laden genutzt (konservativ)
                                         *   - Höhere Werte nutzen mehr Überschuss, können aber zu Netzinstabilität führen
                                         * 
                                         * zielFaktorEntladen: Anteil des Bedarfs der durch Entladen gedeckt wird (0-1)
                                         *   - 1.0 = 100% des Bedarfs werden durch Entladen gedeckt (maximale Autarkie)
                                         *   - Niedrigere Werte schonen den Akku, erhöhen aber Netzbezug
                                         */
                                        const zielFaktorLaden = 0.8;
                                        const zielFaktorEntladen = 1.0;
                                        
                                        // ========== LOGGING SYSTEM ==========
                                        let logHistorie = [];  // Speichert die letzten Log-Einträge für Diagnosezwecke
                                        
                                        /**
                                         * Verbesserte Log-Funktion mit Historie-Speicherung und Level-Steuerung
                                         * 
                                         * @param {string} nachricht - Die zu loggende Nachricht
                                         * @param {string} level - Log-Level ('debug', 'info', 'warn', 'error')
                                         */
                                        function log(nachricht, level = 'info') {
                                            // Erstelle formatierten Log-Eintrag
                                            const timestamp = new Date().toISOString();
                                            const logEintrag = `[${timestamp}] [${level.toUpperCase()}] ${nachricht}`;
                                            
                                            // Füge zur Historie hinzu (begrenzt auf MAX_LOG_LAENGE)
                                            logHistorie.push(logEintrag);
                                            if (logHistorie.length > MAX_LOG_LAENGE) {
                                                logHistorie.shift();
                                            }
                                            
                                            // Ausgabe basierend auf Level und Konfiguration
                                            switch(level) {
                                                case 'error':
                                                    console.error(logEintrag);
                                                    break;
                                                case 'warn':
                                                    console.warn(logEintrag);
                                                    break;
                                                case 'info':
                                                    console.log(logEintrag);
                                                    break;
                                                case 'debug':
                                                    // Debug-Logs nur bei aktiviertem ausführlichen Logging
                                                    if (AUSFUEHRLICHES_LOGGING) {
                                                        console.log(logEintrag);
                                                    }
                                                    break;
                                                default:
                                                    console.log(logEintrag);
                                            }
                                        }
                                        
                                        // ========== HELFERFUNKTIONEN ==========
                                        /**
                                         * Setzt den Steuerungsdatenpunkt mit Fehlerbehandlung
                                         * 
                                         * @param {number} wert - Der zu setzende Wert
                                         * @returns {boolean} True bei Erfolg, False bei Fehler
                                         */
                                        function sicherSetState(wert) {
                                            try {
                                                setState(AKTIONS_DATENPUNKT, wert, false);
                                                return true;
                                            } catch (e) {
                                                log(`Fehler beim Setzen des Datenpunkts: ${e.message}`, 'error');
                                                return false;
                                            }
                                        }
                                        
                                        /**
                                         * Bereinigt den gemessenen Überschuss um den aktuellen Akku-Beitrag
                                         * 
                                         * @param {number} ueberschussGemessen - Gemessener Stromüberschuss
                                         * @param {number} akkuLeistung - Aktuelle Akku-Leistung (positiv = Entladen, negativ = Laden)
                                         * @returns {number} Bereinigter Überschuss
                                         */
                                        function bereinigeLeistung(ueberschussGemessen, akkuLeistung) {
                                            return ueberschussGemessen - akkuLeistung;
                                        }
                                        
                                        // ========== FLEXIBLE REGELUNG ==========
                                        /**
                                         * Startet die kontinuierliche Akku-Regelung
                                         */
                                        function starteFlexibleRegelung() {
                                            if (regelungAktiv) {
                                                log("Regelung bereits aktiv - Start abgebrochen", 'debug');
                                                return;
                                            }
                                            
                                            stoppeRegelung();
                                            log("Starte flexible Regelung...");
                                            
                                            regelungAktiv = true;
                                            mittelungArray = [];
                                            aktuellerSollwert = 0;
                                            integral = 0;
                                            toggleFlag = true;
                                        
                                            // Initialer Status-Log mit Konfiguration
                                            log(`Regelung gestartet mit:
                                          Max Laden: ${maxLadeleistung}W
                                          Max Entladen: ${maxEntladeleistung}W
                                          Laden-Zielfaktor: ${zielFaktorLaden}
                                          Entladen-Zielfaktor: ${zielFaktorEntladen}
                                          SOC-Limits: Ladestopp >${SOC_LADESTOPP}%, Entladestopp <${SOC_ENTLADESTOPP}%`, 'info');
                                        
                                            // Messintervall - Sammelt regelmäßig Leistungsdaten
                                            messIntervall = setInterval(() => {
                                                try {
                                                    const ueberschussGemessen = getState('alias.0.Stromüberschuss_IR-Lesekopf').val;
                                                    const akkuLeistung = getState(AKTUELLE_AKKULEISTUNG).val;
                                                    const bereinigteLeistung = bereinigeLeistung(ueberschussGemessen, akkuLeistung);
                                                    
                                                    mittelungArray.push(bereinigteLeistung);
                                                    if (mittelungArray.length > 6) mittelungArray.shift();
                                                } catch (e) {
                                                    log("Messfehler: " + e.message, 'error');
                                                }
                                            }, samplingIntervall);
                                        
                                            // Regelintervall - Berechnet alle 20 Sekunden neue Sollwerte
                                            regelIntervall = setInterval(() => {
                                                try {
                                                    if (mittelungArray.length < 3) {
                                                        log("Nicht genug Messwerte für Regelung", 'debug');
                                                        return;
                                                    }
                                                    
                                                    // Berechne gleitenden Mittelwert der letzten Messungen
                                                    const summe = mittelungArray.reduce((a, b) => a + b, 0);
                                                    const mittelwert = summe / mittelungArray.length;
                                                    
                                                    // Bestimmung des Betriebsmodus
                                                    const istLadebetrieb = mittelwert > 0; // Positiver Wert = Überschuss = Laden
                                                    const zielFaktor = istLadebetrieb ? zielFaktorLaden : zielFaktorEntladen;
                                                    
                                                    // Berechnung der benötigten Akku-Leistung
                                                    const zielLeistung = Math.abs(mittelwert) * zielFaktor;
                                                    const zielwert = istLadebetrieb ? -zielLeistung : zielLeistung;
                                                    
                                                    // PI-Regler berechnet Anpassung
                                                    const differenz = zielwert - aktuellerSollwert;
                                                    integral += differenz * integralFaktor;
                                                    
                                                    // Integrator-Begrenzung
                                                    const integratorLimit = istLadebetrieb ? maxLadeleistung : maxEntladeleistung;
                                                    integral = Math.min(Math.max(integral, -integratorLimit), integratorLimit);
                                                    
                                                    // Neuen Sollwert berechnen
                                                    let neuerSollwert = aktuellerSollwert + differenz + integral;
                                                    
                                                    // Physikalische Grenzen einhalten
                                                    if (istLadebetrieb) {
                                                        neuerSollwert = Math.min(neuerSollwert, 0); // Max 0 = keine Entladung
                                                        neuerSollwert = Math.max(neuerSollwert, -maxLadeleistung); // Min = -maxLadeleistung
                                                    } else {
                                                        neuerSollwert = Math.max(neuerSollwert, 0); // Min 0 = kein Laden
                                                        neuerSollwert = Math.min(neuerSollwert, maxEntladeleistung); // Max = maxEntladeleistung
                                                    }
                                                    
                                                    // Sanfte Anpassung (30% pro Schritt)
                                                    const aenderung = neuerSollwert - aktuellerSollwert;
                                                    if (Math.abs(aenderung) > 10) {
                                                        aktuellerSollwert += aenderung * 0.3;
                                                    } else {
                                                        aktuellerSollwert = neuerSollwert;
                                                    }
                                                    
                                                    // Ausführliches Logging nur bei Bedarf
                                                    if (AUSFUEHRLICHES_LOGGING) {
                                                        log(`Regelberechnung:
                                          Überschuss (gemittelt): ${mittelwert.toFixed(1)}W
                                          Ziel-Leistung: ${zielLeistung.toFixed(1)}W
                                          Zielwert (Akku): ${zielwert.toFixed(1)}W
                                          Neuer Sollwert: ${neuerSollwert.toFixed(1)}W
                                          Aktueller Sollwert: ${aktuellerSollwert.toFixed(1)}W
                                          Differenz: ${differenz.toFixed(1)}W
                                          Integral: ${integral.toFixed(1)}
                                          Faktor: ${zielFaktor}
                                          Modus: ${istLadebetrieb ? 'Laden' : 'Entladen'}`, 'debug');
                                                    }
                                                } catch (e) {
                                                    log("Regelfehler: " + e.message, 'error');
                                                }
                                            }, 20000);
                                        
                                            // Sendeintervall - Sendet alle 30 Sekunden Steuerbefehle
                                            sendeIntervall = setInterval(() => {
                                                if (!regelungAktiv) return;
                                                
                                                // SOC-Schutz - Verhindert Überladung und Tiefentladung
                                                const akkuSOC = getState('alias.0.Akku_Ladezustand').val;
                                                let effektiverSollwert = aktuellerSollwert;
                                                
                                                if (effektiverSollwert < 0 && akkuSOC > SOC_LADESTOPP) {
                                                    effektiverSollwert = 0;
                                                    log(`LADESTOPP: Akku > ${SOC_LADESTOPP}% (SOC: ${akkuSOC.toFixed(1)}%)`, 'warn');
                                                }
                                                
                                                if (effektiverSollwert > 0 && akkuSOC < SOC_ENTLADESTOPP) {
                                                    effektiverSollwert = 0;
                                                    log(`ENTLADESTOPP: Akku < ${SOC_ENTLADESTOPP}% (SOC: ${akkuSOC.toFixed(1)}%)`, 'warn');
                                                }
                                                
                                                // Alternierende Werte senden (jeder 2. Befehl +0.1W)
                                                const wert = toggleFlag ? effektiverSollwert : effektiverSollwert + 0.1;
                                                sicherSetState(wert);
                                                toggleFlag = !toggleFlag;
                                                
                                                // Protokolliere nur die ersten 20 Minuten oder bei Moduswechsel
                                                log(`Steuerbefehl gesendet: ${wert.toFixed(1)}W (${toggleFlag ? 'nächstes Mal Basiswert' : 'nächstes Mal +0.1W'})`, 'debug');
                                            }, alternierIntervall);
                                        
                                            log("Flexible Regelung erfolgreich gestartet", 'info');
                                        }
                                        
                                        /**
                                         * Stoppt die laufende Regelung
                                         */
                                        function stoppeRegelung() {
                                            if (!regelungAktiv) return;
                                            
                                            clearInterval(messIntervall);
                                            clearInterval(regelIntervall);
                                            clearInterval(sendeIntervall);
                                            regelungAktiv = false;
                                            
                                            sicherSetState(0);
                                            log("Regelung gestoppt", 'info');
                                        }
                                        
                                        // ========== HAUPTPROGRAMM ==========
                                        /**
                                         * Initialisiert das Skript
                                         */
                                        function initSkript() {
                                            try {
                                                starteFlexibleRegelung();
                                                log("Skript initialisierung abgeschlossen");
                                                
                                                // Aktiviere temporäres ausführliches Logging für die ersten 10 Minuten
                                                setTimeout(() => {
                                                    if (!AUSFUEHRLICHES_LOGGING) {
                                                        log("Initiale Debug-Periode beendet - Detaillogs deaktiviert");
                                                    }
                                                }, 600000); // 10 Minuten
                                            } catch (e) {
                                                log("Initialisierungsfehler: " + e.message, 'error');
                                            }
                                        }
                                        
                                        // Startverzögerung für Systeminitialisierung
                                        setTimeout(initSkript, 5000);
                                        
                                        // ========== EVENT-HANDLER ==========
                                        // Überwacht Ladezustandsänderungen
                                        on({ id: 'alias.0.Akku_Ladezustand', change: 'ne' }, (state) => {
                                            const soc = state.val;
                                            
                                            // Protokolliere nur bei relevanten Änderungen
                                            if (soc > SOC_LADESTOPP && aktuellerSollwert < 0) {
                                                log(`AKTION: Ladung pausiert (SOC: ${soc}% > ${SOC_LADESTOPP}%)`, 'warn');
                                            } else if (soc < SOC_ENTLADESTOPP && aktuellerSollwert > 0) {
                                                log(`AKTION: Entladung pausiert (SOC: ${soc}% < ${SOC_ENTLADESTOPP}%)`, 'warn');
                                            } else if (soc <= SOC_LADESTOPP && soc >= SOC_ENTLADESTOPP) {
                                                log(`SOC im normalen Bereich: ${soc}%`, 'debug');
                                            }
                                        });
                                        
                                        // ========== DIAGNOSE & MONITORING ==========
                                        // Regelmäßige Systemdiagnose
                                        const diagIntervall = setInterval(() => {
                                            const ueberschuss = getState('alias.0.Stromüberschuss_IR-Lesekopf').val || 0;
                                            const akkuLeistung = getState(AKTUELLE_AKKULEISTUNG).val || 0;
                                            const akkuSOC = getState('alias.0.Akku_Ladezustand').val || 0;
                                            const istLadebetrieb = aktuellerSollwert < 0;
                                            
                                            log(`Systemdiagnose:
                                          Regelstatus: ${regelungAktiv ? "AKTIV" : "INAKTIV"}
                                          Betriebsmodus: ${istLadebetrieb ? 'LADEN' : 'ENTLADEN'}
                                          Sollleistung: ${aktuellerSollwert.toFixed(1)}W
                                          Istleistung: ${akkuLeistung}W
                                          Überschuss: ${ueberschuss}W
                                          SOC: ${akkuSOC.toFixed(1)}%
                                          Messwerte: ${mittelungArray.length} gespeichert
                                          Letzte Logs: ${logHistorie.slice(-3).join('\n  ')}`, 
                                          'info');
                                        }, 300000); // Alle 5 Minuten
                                        
                                        // Datenpunkt-Prüfung
                                        function pruefeDatenpunkte() {
                                            const datenpunkte = [
                                                'alias.0.Stromüberschuss_IR-Lesekopf',
                                                'alias.0.Akku_Ladezustand',
                                                AKTUELLE_AKKULEISTUNG,
                                                AKTIONS_DATENPUNKT
                                            ];
                                            
                                            let fehler = false;
                                            datenpunkte.forEach(id => {
                                                if (getState(id) === undefined) {
                                                    fehler = true;
                                                    log(`KRITISCH: Datenpunkt ${id} nicht gefunden!`, 'error');
                                                }
                                            });
                                            
                                            if (fehler) {
                                                log("Fehlende Datenpunkte - Skriptfunktionalität eingeschränkt", 'error');
                                            } else {
                                                log("Alle benötigten Datenpunkte verfügbar", 'debug');
                                            }
                                        }
                                        
                                        // Verzögerte Datenpunktprüfung
                                        setTimeout(pruefeDatenpunkte, 15000);
                                        
                                        // ========== NOTFALL-SYSTEM ==========
                                        // Automatischer Neustart bei Inaktivität
                                        const watchdogIntervall = setInterval(() => {
                                            if (!regelungAktiv) {
                                                log("Watchdog: Regelung inaktiv - Neustart wird versucht", 'warn');
                                                initSkript();
                                            }
                                        }, 600000); // Prüft alle 10 Minuten
                                        
                                        A Offline
                                        A Offline
                                        assz
                                        wrote on last edited by assz
                                        #19

                                        Hallo,

                                        ich möchte mich hier mal mit einklinken, um meine Erfahrungen von parallel betriebenen Batteriesystemen zu teilen,
                                        da ich selbst zwei MS-A2 (4,48kwh) als STAND ALLONE parallel zur Bestandsanlage E3DC mit 13kwh Batteriespeicher BETREIBEN MÖCHTE 😉

                                        Grundidee:
                                        Dem MS-A2 nur erlauben UNTERHALB der maximalen Grundlast in der Nacht zu vordefinierten Zeiten den E3DC Hauptsystemspeicher zu entlasten.
                                        Also nur im General tätig sein, wenn genug PV Energie für alles vorhanden ist jedoch auch am Tag im mqtt mode in die Schranken weisen was er darf und nicht.
                                        Dazu aber weiter unten im Text...

                                        Punkt 1: Die deutsche Bürokratie und der NETZBETREIBER
                                        Erstmal... Anfrage beim Netzbetreiber da !Meldepflichtig wegen Anschluss am Niederspannungsnetz!
                                        Hier habe ich aufgrund der nicht wirklichen aufwändigen Installation (Salopp gesagt: Stecker rein...fertig) mal als gewissenhafter Bürger beim Netzbetreiber nach einer vereinfachten Anmeldung via Markstammregister
                                        --- telefonisch, --- per Mail, --- nochmals telefonisch, --- nochmals Mail... mit allen Datenblättern und Fakten 800W Begrenzung und so... angefragt.
                                        Es hieß.... Fachabteilung Einspeisung leitet an Fachabteilung XY weiter... wir melden uns.

                                        Nochmals telefonische Nachfrage auf Bearbeitungsstand da keine Antwort nach 4 Wochen...
                                        Antwort: Ich teile der Fachabteilung mit, dass sie nochmals angerufen haben.

                                        2 Monate später.... ich warte noch immer.
                                        Hier scheint fachlich keiner in der Lage zu sein eine Aussage machen zu können außer evtl. das was ich aus der Vergangenheit schon kenne:
                                        "Bitte wenden sie sich an einen eingetragenen Elektroinstallateur! Bitte senden sie uns eine E8 Inbetriebnahme Erklärung!"

                                        skip this chapter 😉

                                        Punkt 2: Hardware

                                        • 2x MS-A2,
                                          • Shelly 3EM Pro an der Hauptleitung
                                          • Shelly Plug an einer separaten 16A abgesicherten Schuko Steckdose für die beiden in Reihe geschalteten MS-A2
                                        • ioBroker Skript für mqtt Ansteuerung MS-A2 und Signalaustausch E3DC via Modbus
                                          (*by the way... bin EFK)

                                        Punkt 3: Probleme
                                        Wer den MS-A2 einfach "STAND ALLONE" zu seinem bestehenden PV Systemspeicher parallel betreiben möchte um seinen Batteriespeicher kostengünstig zu erweitern
                                        wird feststellen, dass sich die beiden Systeme im GENERAL MODE nicht vertragen.

                                        Was man also in solch einem Vorhaben unbedingt berücksichtigen muss sind Lastwechsel bzw. Zeiten von unzureichender PV Ertrag im General Mode des MS-A2!
                                        Der eine versucht den anderen zu Laden bzw. zu entladen da sie ja nichts voneinander wissen!
                                        Hier muss man schon einiges im Skript einbauen damit das nicht passiert!

                                        Lösungsansätze:

                                        • Bei unzureichendem PV Ertrag oder Lastwechsel von General in mqtt Mode wechseln um ein Eingenleben hart zu unterbinden.
                                        • Nur feste Entladezeiten im mqtt Mode in der Nacht über Astro oder Zeit z.B. 22:00 - 05:00 Uhr = 500W (unterhalb der min Grundlast)
                                        • Trotz Shelly 3EM Pro feste Freigaben definieren wann MS-A2 im General Überschuss laden darf. [PV Überschuss ausreichend?; Hauptspeicher geladen?; Brauchwassererwärmung ausreichen?; E-Fahrzeug wird geladen?; usw... ]
                                        • Entladen grundsätzlich am Tag unterbinden! (Energie ist nur für die Nacht!)
                                          Hier wird schnell klar das auch am Tage der General Mode zwischendurch weichen muss!

                                        Ergo: Man könnte sich theoretisch einen Shelly 3EM Pro an der Hauptleitung sparen und alles komplett über den ioBroker realisieren.

                                        BertDerKleineB H 2 Replies Last reply
                                        1
                                        • A assz

                                          Hallo,

                                          ich möchte mich hier mal mit einklinken, um meine Erfahrungen von parallel betriebenen Batteriesystemen zu teilen,
                                          da ich selbst zwei MS-A2 (4,48kwh) als STAND ALLONE parallel zur Bestandsanlage E3DC mit 13kwh Batteriespeicher BETREIBEN MÖCHTE 😉

                                          Grundidee:
                                          Dem MS-A2 nur erlauben UNTERHALB der maximalen Grundlast in der Nacht zu vordefinierten Zeiten den E3DC Hauptsystemspeicher zu entlasten.
                                          Also nur im General tätig sein, wenn genug PV Energie für alles vorhanden ist jedoch auch am Tag im mqtt mode in die Schranken weisen was er darf und nicht.
                                          Dazu aber weiter unten im Text...

                                          Punkt 1: Die deutsche Bürokratie und der NETZBETREIBER
                                          Erstmal... Anfrage beim Netzbetreiber da !Meldepflichtig wegen Anschluss am Niederspannungsnetz!
                                          Hier habe ich aufgrund der nicht wirklichen aufwändigen Installation (Salopp gesagt: Stecker rein...fertig) mal als gewissenhafter Bürger beim Netzbetreiber nach einer vereinfachten Anmeldung via Markstammregister
                                          --- telefonisch, --- per Mail, --- nochmals telefonisch, --- nochmals Mail... mit allen Datenblättern und Fakten 800W Begrenzung und so... angefragt.
                                          Es hieß.... Fachabteilung Einspeisung leitet an Fachabteilung XY weiter... wir melden uns.

                                          Nochmals telefonische Nachfrage auf Bearbeitungsstand da keine Antwort nach 4 Wochen...
                                          Antwort: Ich teile der Fachabteilung mit, dass sie nochmals angerufen haben.

                                          2 Monate später.... ich warte noch immer.
                                          Hier scheint fachlich keiner in der Lage zu sein eine Aussage machen zu können außer evtl. das was ich aus der Vergangenheit schon kenne:
                                          "Bitte wenden sie sich an einen eingetragenen Elektroinstallateur! Bitte senden sie uns eine E8 Inbetriebnahme Erklärung!"

                                          skip this chapter 😉

                                          Punkt 2: Hardware

                                          • 2x MS-A2,
                                            • Shelly 3EM Pro an der Hauptleitung
                                            • Shelly Plug an einer separaten 16A abgesicherten Schuko Steckdose für die beiden in Reihe geschalteten MS-A2
                                          • ioBroker Skript für mqtt Ansteuerung MS-A2 und Signalaustausch E3DC via Modbus
                                            (*by the way... bin EFK)

                                          Punkt 3: Probleme
                                          Wer den MS-A2 einfach "STAND ALLONE" zu seinem bestehenden PV Systemspeicher parallel betreiben möchte um seinen Batteriespeicher kostengünstig zu erweitern
                                          wird feststellen, dass sich die beiden Systeme im GENERAL MODE nicht vertragen.

                                          Was man also in solch einem Vorhaben unbedingt berücksichtigen muss sind Lastwechsel bzw. Zeiten von unzureichender PV Ertrag im General Mode des MS-A2!
                                          Der eine versucht den anderen zu Laden bzw. zu entladen da sie ja nichts voneinander wissen!
                                          Hier muss man schon einiges im Skript einbauen damit das nicht passiert!

                                          Lösungsansätze:

                                          • Bei unzureichendem PV Ertrag oder Lastwechsel von General in mqtt Mode wechseln um ein Eingenleben hart zu unterbinden.
                                          • Nur feste Entladezeiten im mqtt Mode in der Nacht über Astro oder Zeit z.B. 22:00 - 05:00 Uhr = 500W (unterhalb der min Grundlast)
                                          • Trotz Shelly 3EM Pro feste Freigaben definieren wann MS-A2 im General Überschuss laden darf. [PV Überschuss ausreichend?; Hauptspeicher geladen?; Brauchwassererwärmung ausreichen?; E-Fahrzeug wird geladen?; usw... ]
                                          • Entladen grundsätzlich am Tag unterbinden! (Energie ist nur für die Nacht!)
                                            Hier wird schnell klar das auch am Tage der General Mode zwischendurch weichen muss!

                                          Ergo: Man könnte sich theoretisch einen Shelly 3EM Pro an der Hauptleitung sparen und alles komplett über den ioBroker realisieren.

                                          BertDerKleineB Offline
                                          BertDerKleineB Offline
                                          BertDerKleine
                                          wrote on last edited by
                                          #20

                                          @assz sagte in Erfahrung: Hoymiles MS-A2 Akku via MQTT iobroker steuern:

                                          Ergo: Man könnte sich theoretisch einen Shelly 3EM Pro an der Hauptleitung sparen und alles komplett über den ioBroker realisieren.

                                          Absolut. Mein Skript oben tut ja im Prinzip genau das per MQTT über iobroker, was die herstellereigene Logik im "general mode" macht.
                                          Ich habe auch gar keinen Shelly 3EM Pro an der Hauptleitung, sondern benutze einen Wattwächter direkt auf dem offiziellen Zähler, der sich über MQTT und die Software Uni-Meter als Shelly 3EM ausgibt.

                                          Das Skript oben habe ich dann direkt an den MQTT Datenpunkt des Wattwächters angebunden.

                                          Statt sowas wie Wattwächter kann man natürlich jede iobroker Datenquelle für bezogene oder eingespeiste Leistung benutzen, die man hat (z.B. aus der PV Anlage), daher schreibe ich oben ja einfach von einem Alias.

                                          Du kannst das obige MQTT Skript so erweitern, wie es für Dein Szenario passt. Du brauchst dazu halt zusätzliche Datenpunkte direkt aus Deinem Systemspeicher.

                                          Wie bei @hschief hast Du auch ein ganz spezifisches, anderes Szenario, das eigene Logik benötigt. Wenn Du die Logik für Dich mal klar hast, dürfte das Skripten der einfachere Teil werden.

                                          A 1 Reply Last reply
                                          0
                                          Reply
                                          • Reply as topic
                                          Log in to reply
                                          • Oldest to Newest
                                          • Newest to Oldest
                                          • Most Votes


                                          Support us

                                          ioBroker
                                          Community Adapters
                                          Donate

                                          612

                                          Online

                                          32.4k

                                          Users

                                          81.3k

                                          Topics

                                          1.3m

                                          Posts
                                          Community
                                          Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen
                                          ioBroker Community 2014-2025
                                          logo
                                          • Login

                                          • Don't have an account? Register

                                          • Login or register to search.
                                          • First post
                                            Last post
                                          0
                                          • Recent
                                          • Tags
                                          • Unread 0
                                          • Categories
                                          • Unreplied
                                          • Popular
                                          • GitHub
                                          • Docu
                                          • Hilfe