SPH 4600 Growatt Modbus 485 -> openHAB

Für welche Projekte verwendet Ihr OpenHAB? Was habt Ihr automatisiert? Stellt eure Projekte hier vor.

Moderatoren: Cyrelian, seppy

mad-mike
Beiträge: 457
Registriert: 6. Jan 2021 18:05
Answers: 3

Re: SPH 4600 Growatt Modbus 485 -> openHAB

Beitrag von mad-mike »

Moin,

Bin einfach blind und finde den Fehler nicht, entweder Schaltet der Growatt bei jedem changed oder Garnicht....

Code: Alles auswählen

rule "Growatt OFF"

when
    Item SOC changed  
then
//    if(!(newState instanceof Number)) 
//        logWarn("SOC","Ungültige Wert ({})! Abbruch.",newState)
//        return;
        if(day.state  == ON)                                                    // Item daylight  
        return;
        if(Standby_PVnr.state  == 0)                                            // Abfrage Growatt Standby  
        return;
        if(SOC_set.state <= SOC.state)
        return;
        
    Standby_PVnr.sendCommand(0)                                                 // Standby ON
    Status_Akku.postUpdate("Standby")                                           // TAB meldung
    sendBroadcastNotification("Growatt OFF")                                    // Nachricht

end

Also ziel ist:

Wenn der Akku leer ist, den Growatt in den Standby zu schicken. Das soll nur Passieren wenn daylight End ist. Hier habe ich ein Item erstellt, ON und OFF ist...


kann jemand Helfen??

Danke und schönes Wochenende
Gruss mad-mike

openHABian 4.2.2 auf Raspberry Pi 4 Mod. b (8GB) ;)

Benutzeravatar
udo1toni
Beiträge: 14829
Registriert: 11. Apr 2018 18:05
Answers: 232
Wohnort: Darmstadt

Re: SPH 4600 Growatt Modbus 485 -> openHAB

Beitrag von udo1toni »

Ich denke mal, Du musst hier explizit nach Number casten, deshalb sauber so:

Code: Alles auswählen

rule "Growatt OFF"
when
    Item SOC changed  
then
    if(!(newState instanceof Number)) {
        logWarn("growatt","SOC liefert keine Zahl ({})! Abbruch.", newState)
        return;
    }
    if(!(SOC_set.state instanceof Number)) {
        logWarn("growatt","SOC liefert keine Zahl ({})! Abbruch.", SOC_set.state)
        return;
    }
    val nMin = SOC_set.state as Number

    if(day.state == ON)                                                           // Item daylight  
        return;

    if(Standby_PVnr.state == 0)                                                   // Abfrage Growatt Standby
        return;

    if(nMin <= (newState as Number))
        return;

    Standby_PVnr.sendCommand(0)                                                   // Standby ON
    Status_Akku.postUpdate("Standby")                                             // TAB meldung
    sendBroadcastNotification("Growatt OFF")                                      // Nachricht
end
Die Überprüfung auf Number ist schon wichtig, aber wenn Du dann eine Meldung ausgeben willst, musst Du natürlich den bedingten Block in {} setzen, sonst wird das return; immer ausgeführt...
openHAB4.2.2 stable in einem Debian-Container (bookworm) (Proxmox 8.2.8, LXC), mit openHABian eingerichtet

mad-mike
Beiträge: 457
Registriert: 6. Jan 2021 18:05
Answers: 3

Re: SPH 4600 Growatt Modbus 485 -> openHAB

Beitrag von mad-mike »

Danke dir, Also ich komme einfach nicht dahinter, was ich falsch mache... :cry:

Ich habe dem Vergleich noch ein -1 ergänzt.

So hat der Growatt heute das erste mal abeschalten:

Code: Alles auswählen

rule "Growatt OFF"
when
    Item SOC changed  
then
    if(!(newState instanceof Number)) {
        logWarn("growatt","SOC liefert keine Zahl ({})! Abbruch.", newState)
        return;
    }
    if(!(SOC_set.state instanceof Number)) {
        logWarn("growatt","SOC liefert keine Zahl ({})! Abbruch.", SOC_set.state)
        return;
    }
    val nSOC_set = (SOC_set.state as Number).intValue
    val nSOC = (SOC.state as Number).intValue

    if(day.state == ON)                                                           // Item daylight  
        return;

    if(Standby_PVnr.state == 0)                                                   // Abfrage Growatt Standby
        return;

    if((nSOC - 1) >= nSOC_set)
        return;

    Standby_PVnr.sendCommand(0)                                                   // Standby ON
    Status_Akku.postUpdate("Standby")                                             // TAB meldung
    sendBroadcastNotification("Growatt OFF")                                      // Nachricht
end
Gruss mad-mike

openHABian 4.2.2 auf Raspberry Pi 4 Mod. b (8GB) ;)

Benutzeravatar
udo1toni
Beiträge: 14829
Registriert: 11. Apr 2018 18:05
Answers: 232
Wohnort: Darmstadt

Re: SPH 4600 Growatt Modbus 485 -> openHAB

Beitrag von udo1toni »

Wir hatten es ja in einem anderen Thread davon.... Du könntest spaßeshalber die Variablen explizit als Number oder Integer definieren.
Standby_PVnr.state könnte man (da es sich ja um einen einzelnen fixen Wert handelt) vielleicht auch als String überprüfen:

Code: Alles auswählen

if(Standby_PVnr.state.toString == "0")
Und natürlich könnte man zur besseren Abgrenzung, welche der Abbruchbedingungen nun dazu geführt hat, dass nicht abgeschaltet wurde, diese ebenfalls mit logs versehen, vielleicht in der Form

Code: Alles auswählen

logDebug("growatt","<Grund für Abbruch>. Abbruch.")
Dann kannst Du, wenn mal alles exakt so läuft wie vorgesehen, einfach das Log Level auf WARN oder INFO erhöhen und die Meldungen kommen nicht mehr, zum Testen setzt Du das Log Level aber auf DEBUG und siehst jedes Detail.
openHAB4.2.2 stable in einem Debian-Container (bookworm) (Proxmox 8.2.8, LXC), mit openHABian eingerichtet

mad-mike
Beiträge: 457
Registriert: 6. Jan 2021 18:05
Answers: 3

Re: SPH 4600 Growatt Modbus 485 -> openHAB

Beitrag von mad-mike »

udo1toni hat geschrieben: 13. Jul 2023 21:37
Mein Gegenangebot :) :

Code: Alles auswählen

// Globale Variablen zu Beginn der Datei definieren!
var Timer   tBwwp = null
var Integer iBwwp = 0                                                                             // 0 = neutral, 1 = stoppen, 2 = starten

rule "bwwp laden"
when
    Item PV_Einspeisung changed                                                                   // Freigabe BWWP
then
    if(!(newState instanceof Number))                                                             // aktuell keine Zahl?
        return;
    val nCur = (newState as Number).intValue                                                      // aktueller Wert, Integer soll reichen

    if(nCur < 700 && iBwwp != 1) {                                                                // Stoppbedingung und Timer nicht in Stopp Modus
        logInfo("bwwp","Stoppbedingung erkannt! Leistung = {}",nCur)
        iBwwp = 1                                                                                 // Modus auf Stopp
        tBwwp?.cancel                                                                             // falls Timer vorhanden, cancel
        tBwwp = createTimer(now.plusMinutes(1), [|                                                // Timer neu anlegen und nach einer Sekunde starten
            if(dry_contact.state != OFF) {                                                        // falls dry_contact nicht OFF
                if(!dry_contact.changedSince(now.minusMinutes(30))) {                             // ist letzte Änderung mehr als 309 Minuten her?
                    logInfo("bwwp","Stopp erkannt, Pumpe läuft mindestens 30 Minuten! Stoppe")
                    dry_contact.sendCommand(OFF)                                                  // dann OFF-Command senden
                } else {                                                                           // ansonsten
                    logInfo("bwwp","Stopp erkannt, Pumpe läuft weniger als 30 Minuten! Warte...")
                    tBwwp.reschedule(now.plusMinutes(1))                                          // timer in einer Minute wieder ausführen
                }
            }
        ])
    }
    if(nCur > 1200 && iBwwp != 2) {                                                               // Startbedingung und Timer nicht in Start Modus
        logInfo("bwwp","Startbedingung erkannt! Leistung = {}",nCur)
        iBwwp = 2                                                                                 // Modus auf Start
        tBwwp?.cancel                                                                             // falls Timer vorhanden, cancel
        tBwwp = createTimer(now.plusMinutes(5), [|                                                // in 5 Minuten
            if(dry_contact.state != ON) {                                                         // falls nicht ON
                logInfo("bwwp","Starte Pumpe")
                dry_contact.sendCommand(ON)                                                       // einschalten
            }
        ])
    }
    if(nCur >= 700 && iBwwp == 1) {                                                               // falls Stopp Modus aktiv und über 700
        logInfo("bwwp","Abbruch Stoppbedingung erkannt! Leistung = {}",nCur)
        iBwwp = 0                                                                                 // Modus auf nix
        tBwwp?.cancel                                                                             // falls Timer vorhanden, cancel
    }
end
Noch mal eine Frage dazu:

Erstmal vorweg: Läuft perfekt. ;)

Ich möchte die exakt diese Regel noch mal verwenden. Aber diese soll ein anderen aufgaben Bereich bekommen.

Der Trigger soll der gleiche sein. :!: Das einfachste Wäre für mich, Die Rule einfach noch mal zu kopieren, und dann halt mit anderen Werten füttern.

Sollte man das tun? :?: Oder sollte man das anderes lösen?

Code: Alles auswählen

// Globale Variablen zu Beginn der Datei definieren!
var Timer   tBwwp = null
var Integer iBwwp = 0                                                                             // 0 = neutral, 1 = stoppen, 2 = starten

rule "bwwp laden"
when
    Item PV_total changed                                                                       // Freigabe BWWP
then
    if(!(newState instanceof Number))                                                             // aktuell keine Zahl?
        return;
    val nCur = ((newState as Number).intValue                                                     // aktueller Wert, Integer soll reichen
    )
    if(nCur < 750 && iBwwp != 1) {                                                                // Stoppbedingung und Timer nicht in Stopp Modus
        logInfo("bwwp","Stoppbedingung erkannt! Leistung = {}",nCur)
        iBwwp = 1                                                                                 // Modus auf Stopp
        logInfo.postUpdate("iBwwp = 1")                                                                                 // Modus auf Stopp
        tBwwp?.cancel                                                                             // falls Timer vorhanden, cancel
        tBwwp = createTimer(now.plusMinutes(5), [|                                                // Timer neu anlegen und nach einer Sekunde starten
            if(Zigbee_Steckdose_1.state != OFF) {                                                        // falls Zigbee_Steckdose_1 nicht OFF
                if(!Zigbee_Steckdose_1.changedSince(now.minusMinutes(60))) {                             // ist letzte Änderung mehr als 30 Minuten her?
                    logInfo("bwwp","Stopp erkannt, Pumpe läuft mindestens 60 Minuten! Stoppe")
                    Zigbee_Steckdose_1.sendCommand(OFF)                                                  // dann OFF-Command senden
                    sendBroadcastNotification("BWWP OFF ")                                        // Gib Meldung aus
                } else {                                                                          // ansonsten
                    logInfo("bwwp","Stopp erkannt, Taktsperre")
                    tBwwp.reschedule(now.plusMinutes(5))                                          // timer in einer Minute wieder ausführen
                }
            }
        ])
    }
    if(nCur > 2000 && iBwwp != 2) {                                                               // Startbedingung und Timer nicht in Start Modus
        logInfo("bwwp","Startbedingung erkannt! Leistung = {}",nCur)
        logInfo.postUpdate("iBwwp = 2")                                                                                 // Modus auf Start
        iBwwp = 2                                                                                 // Modus auf Stopp
        tBwwp?.cancel                                                                             // falls Timer vorhanden, cancel
        tBwwp = createTimer(now.plusMinutes(5), [|                                                // in 5 Minuten
            if(Zigbee_Steckdose_1.state != ON) {                                                  // falls nicht ON
                if(!Zigbee_Steckdose_1.changedSince(now.minusMinutes(60))) {                             // ist letzte Änderung mehr als 30 Minuten her?4
                logInfo("bwwp","Starte Pumpe")
                Zigbee_Steckdose_1.sendCommand(ON)                                                // einschalten
                sendBroadcastNotification("BWWP ON ")                                             // Gib Meldung aus
                
            } else {                                                                          // ansonsten
                logInfo("bwwp","Start erkannt, Taktsperre")
                tBwwp.reschedule(now.plusMinutes(5))                                          // timer in einer Minute wieder ausführen
                }                
            }
        ])
    }
    if(nCur >= 1000 && iBwwp == 1) {                                                               // falls Stopp Modus aktiv und über 1000
        logInfo("bwwp","Abbruch Stoppbedingung erkannt! Leistung = {}",nCur)
        logInfo.postUpdate("iBwwp = 0")                                                                                 // Modus auf nix
        iBwwp = 0                                                                                 // Modus auf Stopp
        tBwwp?.cancel                                                                             // falls Timer vorhanden, cancel

    }
end
So sieht es jetzt aus.

Dort könnte ich auch einen "auswerte" Bereich von ~100 - 300Watt machen und einen Status um Punkt 3 - 4 - 5 erweitern?? Dann wird die Regel nicht doppelt ausgeführt werden. Wenn das überhaut eine Rolle spielt...
Gruss mad-mike

openHABian 4.2.2 auf Raspberry Pi 4 Mod. b (8GB) ;)

Benutzeravatar
udo1toni
Beiträge: 14829
Registriert: 11. Apr 2018 18:05
Answers: 232
Wohnort: Darmstadt

Re: SPH 4600 Growatt Modbus 485 -> openHAB

Beitrag von udo1toni »

Die Fragestellung ist leider zu ungenau :)

Aber zuerst: Du hast da ein Item mit dem Namen logInfo. Mach das bitte nicht!
Erst mal kommt man selbst schnell durcheinander, denn logInfo kennt nun mal keine Methode .postUpdate(). Und obwohl das momentan offensichtlich bei Dir funktioniert, gibt es keine Langzeitgarantie, denn logInfo ist ein reserviertes Schlüsselwort, es kann gut passieren, dass die Rules DSL das irgendwann einfach als Fehler behandelt, und dann stehst Du da und verstehst nicht, warum die Rule plötzlich nicht mehr funktioniert.
Also besser einen anderen Namen für solch ein Item wählen, z.B. infoLog oder logInformation.

Nun zur eigentlichen Frage: Was meinst Du mit "anderer Aufgabenbereich"? Willst Du (unabhängig) einen zusätzlichen Verbraucher schalten?
So wie die Rule momentan gestrickt ist, ist sie eigentlich unnötig kompliziert. Ich bin jetzt zu faul, nachzuschauen, von wo wir ursprünglich kamen...
Jedenfalls möchtest Du im Grunde maximal einen Schaltvorgang pro Stunde haben, und zwar unabhängig von der Schaltrichtung. Ich denke, ursprünglich waren die Anforderungen andere, weshalb ON und OFF auch unterschiedlich behandelt wurden.
Mein aktuelles Angebot (ohne eine Erweiterung um die unbekannten zusätzlichen Funktionen):

Code: Alles auswählen

// Globale Variablen zu Beginn der Datei definieren!
var Timer   tBwwp = null
var Integer iBwwp = 0                                                                             // 0 = neutral, 1 = stoppen, 2 = starten

rule "PV_total changed"
when
    Item PV_total changed                                                                         // Freigabe BWWP
then
    if(!(newState instanceof Number))                                                             // aktuell keine Zahl?
        return;
    val nCur = ((newState as Number).intValue                                                     // aktueller Wert, Integer soll reichen
    )
    if(nCur <   750 && iBwwp != 1) { iBwwp = 1 } else                                             // Stoppbedingung und Timer nicht in Stopp Modus
    if(nCur >  2000 && iBwwp != 2) { iBwwp = 2 } else                                             // Startbedingung und Timer nicht in Start Modus
    if(nCur >= 1000 && iBwwp == 1) { iBwwp = 0 }                                                  // falls Stopp Modus aktiv und über 1000
    else return;                                                                                  // Modus nicht verändert, Abbruch

    val strBwwp = if(iBwwp == 0) "neutral" else if(iBwwp == 1) "Stopp" else "Start"               // Meldetext generieren
    logInfo("bwwp","Leistung = {}, {}",nCur,strBwwp)
    infoLog.postUpdate("iBwwp = " + iBwwp.toString + " (" + strBwwp * ")")

    tBwwp?.cancel                                                                                 // bestehende Timer entfernen
    if(iBwwp > 0) {                                                                               // Muss Timer erzeugt werden?
        tBwwp = createTimer(now.plusMinutes(5), [|                                                // Timer anlegen, Start nach fünf Minuten
            val strSoll = if(iBwwp == 1) "Stopp" else "Start"
            if(Zigbee_Steckdose_1.changedSince(now.minusMinutes(60))) {                           // ist letzte Änderung weniger als 60 Minuten her?
                logInfo("bwwp","Taktsperre, {} gefordert",strSoll)
                tBwwp.reschedule(now.plusMinutes(5))                                              // Timer in 5 Minuten wieder ausführen
            } else {
                val swSoll = if(iBwwp == 1) OFF else ON
                if(Zigbee_Steckdose_1.state != swSoll) {                                          // Falls Zustand von Soll abweicht
                    val strIst = if(swSoll == ON) "steht" else "läuft"
                    Zigbee_Steckdose_1.sendCommand(swSoll.toString)  
                    logInfo("bwwp","Pumpe {} mindestens 60 Minuten, {}e!",strIst,strSoll.toLower)
                    sendBroadcastNotification("BWWP " + swSoll.toString)                          // Gib Meldung aus
                }
            }
        ])
    }
end
Die Rule entscheidet zunächst, welcher Modus gefordert ist. Ergibt sich daraus keine Änderung des Zustands, bricht die Rule ab (letztes else ohne weitere Bedingungen). Wurde der Modus geändert, wird eine Meldung generiert, ein bestehender Timer beendet und, falls Modus 1 oder 2 gewählt ist, neu angelegt.
Innerhalb des Timers wird die Taktung geprüft und gegebenenfalls eine Pause eingelegt. Ist die letzte Umschaltung mindestens 60 Minuten her, prüft der Code ob ein Schaltvorgang notwendig ist und führt diesen gegebenenfalls aus. Falls eine Umschaltung erfolgt, wird eine Meldung generiert.

Ein guter Teil des Codes wird für die Meldungen benötigt :) aber zumindest ist es schon etwas weniger als in der alten Rule, 40 Zeilen vs. 57 Zeilen, wobei ein Teil natürlich auf die geänderte Formatierung zurückgeht.

Was die zusätzliche Funktion betrifft, so gibt es eine einfache Faustregel: Ist der Trigger identisch, baut man die Funktion in die bestehende Rule ein.
openHAB4.2.2 stable in einem Debian-Container (bookworm) (Proxmox 8.2.8, LXC), mit openHABian eingerichtet

mad-mike
Beiträge: 457
Registriert: 6. Jan 2021 18:05
Answers: 3

Re: SPH 4600 Growatt Modbus 485 -> openHAB

Beitrag von mad-mike »

Aber zuerst: Du hast da ein Item mit dem Namen logInfo. Mach das bitte nicht!
werde ich ändern... :!: aktuell funktioniert es.
Die Fragestellung ist leider zu ungenau :)
Deine zuvor genannte Rule soll für die Brauchwasser WP sein. :!: (Läuft noch nicht wegen fehler:

Code: Alles auswählen

2024-05-25 15:01:27.304 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'bwwp1-1' failed: Unknown variable or command '*'; line 20, column 61, length 13 in bwwp1
)
Willst Du (unabhängig) einen zusätzlichen Verbraucher schalten?
Ja genau,
Ich möchte die Gleiche Rule noch mal einbringen für das schalten von Lampen. Hierzu wird eine andere Zigbee Steckdose geschalten, Und dazu werden auch andere Werte des Triggers benötigt.


Ich habe das mal überflogen geändert:

Code: Alles auswählen

// Globale Variablen zu Beginn der Datei definieren!
var Timer   tlight = null
var Integer ilight = 0                                                                             // 3 = neutral, 4 = stoppen, 5 = starten

rule "PV_total changed"
when
    Item PV_total changed                                                                         // Freigabe light
then
    if(!(newState instanceof Number))                                                             // aktuell keine Zahl?
        return;
    val nCur = ((newState as Number).intValue                                                     // aktueller Wert, Integer soll reichen
    )
    if(nCur <   100 && ilight != 4) { ilight = 4 } else                                             // Stoppbedingung und Timer nicht in Stopp Modus
    if(nCur >   300 && ilight != 5) { ilight = 5 } else                                             // Startbedingung und Timer nicht in Start Modus
    if(nCur >=  150 && ilight == 4) { ilight = 3 }                                                  // falls Stopp Modus aktiv und über 1000
    else return;                                                                                  // Modus nicht verändert, Abbruch

    val light = if(ilight == 3) "neutral" else if(ilight == 4) "Stopp" else "Start"               // Meldetext generieren
    logInfo("light","Leistung = {}, {}",nCur,strlight)
    logInfo.postUpdate("ilight = " + ilight.toString + " (" + strlight * ")")

    tlight?.cancel                                                                                 // bestehende Timer entfernen
    if(ilight > 3) {                                                                               // Muss Timer erzeugt werden?
        tlight = createTimer(now.plusMinutes(5), [|                                                // Timer anlegen, Start nach fünf Minuten
            val strSoll = if(ilight == 4) "Stopp" else "Start"
            if(Zigbee_Steckdose_2.changedSince(now.minusMinutes(60))) {                           // ist letzte Änderung weniger als 60 Minuten her?
                logInfo("light","Taktsperre, {} gefordert",strSoll)
                tlight.reschedule(now.plusMinutes(5))                                              // Timer in 5 Minuten wieder ausführen
            } else {
                val swSoll = if(ilight == 4) ON else OFF
                if(Zigbee_Steckdose_2.state != swSoll) {                                          // Falls Zustand von Soll abweicht
                    val strIst = if(swSoll == OFF) "steht" else "läuft"
                    Zigbee_Steckdose_2.sendCommand(swSoll.toString)  
                    logInfo("light","Pumpe {} mindestens 60 Minuten, {}e!",strIst,strSoll.toLower)
                    sendBroadcastNotification("light " + swSoll.toString)                          // Gib Meldung aus
                }
            }
        ])
    }
end



Danke und schönes Wochenende.
Gruss mad-mike

openHABian 4.2.2 auf Raspberry Pi 4 Mod. b (8GB) ;)

Benutzeravatar
udo1toni
Beiträge: 14829
Registriert: 11. Apr 2018 18:05
Answers: 232
Wohnort: Darmstadt

Re: SPH 4600 Growatt Modbus 485 -> openHAB

Beitrag von udo1toni »

mad-mike hat geschrieben: 25. Mai 2024 15:47 Deine zuvor genannte Rule soll für die Brauchwasser WP sein. :!: (Läuft noch nicht wegen fehler:
Jepp, Tippfehler (mindestens ein schwerwiegender ist immer drin...), es muss natürlich ein + sein, kein * (zufällig auf der gleichen Taste...)Die Zeile generiert ja einen zusammengesetzten String.

Wie oben erwähnt wäre es sinnvoller, eine Rule zu erstellen. Nun sollen die Funktionen ja unabhängig voneinander sein, also ist eines der "Probleme", dass ein return; hier leider nicht funktioniert. Dieser Teil kommt zum Einsatz, wenn sich der Wert von iBwwp nicht verändert hat, das lässt sich leicht ermitteln, indem wir zu Beginn der Rule den Wert sichern. Dann sieht die Rule so aus:

Code: Alles auswählen

// Globale Variablen zu Beginn der Datei definieren!
var Timer   tBwwp  = null
var Timer   tLight = null
var Integer iBwwp  = 0                                                                                // 0 = neutral, 1 = stoppen, 2 = starten
var Integer iLight = 3                                                                                // 3 = neutral, 4 = stoppen, 5 = starten


rule "PV_total changed"
when
    Item PV_total changed                                                                             // Freigabe BWWP
then
    if(!(newState instanceof Number))                                                                 // aktuell keine Zahl?
        return;
    val nCur = (newState as Number).intValue                                                          // aktueller Wert, Integer soll reichen
    val iBwwpOld  = iBwwp
    val iLightOld = iLight

    if(nCur <   750 ) { iBwwp  = 1 } else                                               // Stoppbedingung und Timer nicht in Stopp Modus
    if(nCur >  2000 ) { iBwwp  = 2 } else                                               // Startbedingung und Timer nicht in Start Modus
    if(nCur >= 1000 && iBwwp  == 1) { iBwwp  = 0 }                                                    // falls Stopp Modus aktiv und über 1000
    if(nCur <   100 ) { iLight = 4 } else                                               // Stoppbedingung und Timer nicht in Stopp Modus
    if(nCur >   300 ) { iLight = 5 } else                                               // Startbedingung und Timer nicht in Start Modus
    if(nCur >=  150 && iLight == 4) { iLight = 3 }                                                    // falls Stopp Modus aktiv und über 1000

    if(iBwwpOld  != iBwwp) {                                                                          // Brauchwasser Modus verändert, Timer update
        val strBwwp = if(iBwwp == 0) "neutral" else if(iBwwp == 1) "Stopp" else "Start"               // Meldetext generieren
        logInfo("bwwp","Leistung = {}, {}",nCur,strBwwp)
        infoLog.postUpdate("iBwwp = " + iBwwp.toString + " (" + strBwwp + ")")

        tBwwp?.cancel                                                                                 // bestehende Timer entfernen
        if(iBwwp > 0) {                                                                               // Muss Timer erzeugt werden?
            tBwwp = createTimer(now.plusMinutes(5), [|                                                // Timer anlegen, Start nach fünf Minuten
                val strSoll = if(iBwwp == 1) "Stopp" else "Start"
                if(Zigbee_Steckdose_1.changedSince(now.minusMinutes(60))) {                           // ist letzte Änderung weniger als 60 Minuten her?
                    logInfo("bwwp","Taktsperre, {} gefordert",strSoll)
                    tBwwp.reschedule(now.plusMinutes(5))                                              // Timer in 5 Minuten wieder ausführen
                } else {
                    val swSoll = if(iBwwp == 1) OFF else ON
                    if(Zigbee_Steckdose_1.state != swSoll) {                                          // Falls Zustand von Soll abweicht
                        val strIst = if(swSoll == ON) "steht" else "läuft"
                        Zigbee_Steckdose_1.sendCommand(swSoll.toString)  
                        logInfo("bwwp","Pumpe {} mindestens 60 Minuten, {}e!",strIst,strSoll.toLower)
                        sendBroadcastNotification("BWWP " + swSoll.toString)                          // Gib Meldung aus
                    }
                }
            ])
        }
    }

    if(iLightOld != iLight) {
        val strLight = if(iLight == 3) "neutral" else if(iLight == 4) "Stopp" else "Start"            // Meldetext generieren
        logInfo("light","Leistung = {}, {}",nCur,strLight)
        infoLog.postUpdate("iLight = " + iLight.toString + " (" + strLight + ")")

        tlight?.cancel                                                                                // bestehende Timer entfernen
        if(iLight > 3) {                                                                              // Muss Timer erzeugt werden?
            tlight = createTimer(now.plusMinutes(5), [|                                               // Timer anlegen, Start nach fünf Minuten
                val strSoll = if(iLight == 4) "Stopp" else "Start"
                if(Zigbee_Steckdose_2.changedSince(now.minusMinutes(60))) {                           // ist letzte Änderung weniger als 60 Minuten her?
                    logInfo("light","Taktsperre, {} gefordert",strSoll)
                    tlight.reschedule(now.plusMinutes(5))                                             // Timer in 5 Minuten wieder ausführen
                } else {
                    val swSoll = if(iLight == 4) ON else OFF
                    if(Zigbee_Steckdose_2.state != swSoll) {                                          // Falls Zustand von Soll abweicht
                        val strIst = if(swSoll == OFF) "steht" else "läuft"
                        Zigbee_Steckdose_2.sendCommand(swSoll.toString)  
                        logInfo("light","Pumpe {} mindestens 60 Minuten, {}e!",strIst,strSoll.toLower)
                        sendBroadcastNotification("light " + swSoll.toString)                         // Gib Meldung aus
                    }
                }
            ])
        }
    }
end
wobei ich noch nicht alles komplett auf Fehler durchsucht habe. Aber zunächst: Warum 3,4,5? Die Zahl muss (und sollte) sich nicht von der ersten Funktion unterscheiden, sind ja schließlich identische Funktionen...
Ansonsten wäre es vermutlich einfacher, die Funktion umzubauen und nur einen Timer zu verwenden. Die Frage ist aber auch, ob die Menge an Log Meldungen wirklich zielführend ist.
openHAB4.2.2 stable in einem Debian-Container (bookworm) (Proxmox 8.2.8, LXC), mit openHABian eingerichtet

mad-mike
Beiträge: 457
Registriert: 6. Jan 2021 18:05
Answers: 3

Re: SPH 4600 Growatt Modbus 485 -> openHAB

Beitrag von mad-mike »

Warum 3,4,5?
Weil ich mir so dachte, wenn 0,1,2 doppelt verwendet wird, dann würde der Status wechseln, für die Lampe, ob wohl es für die BWWP sein soll??

Wenn du sagst, das meine überlegung Blödsinn ist. dann lerne ich natürlich dazu... (ich habe soviele Ideen im Kopf, aber diese Art der Programmierung, fällt mir schwer :mrgreen: Aber das ist ein anderes Thema)

Jedenfalls, wenn das alles einfacher geht, Natürlich.


Ich versuche es noch einmal zu beschreiben:
Der Trigger meldet etwa im Sekundentakt die PV Leistung.

Diese Werte nutze ich auch um die Lampen im Flur zuschalten. Primitiv einfach: Wenn Leistung über 300 Watt -> Hell genug -> Lampe aus. Natürlich gabs hier auch mal das Problem wenn eine Wolke vorbei zieht -> Lampe an, Lampe aus. Weiter habe ich derzeit eine Sperre drin. Wenn ich die Lampe manuell schalte, kann die automatik 1ne Stunde diese lampe nicht ausschalten...


Weiter Nutze ich den Trigger diese Werte um Die BWWP ausschließlich über die PV zu Laden.

Dazu nutze ich derzeit 2 Rule, beide mit dem selbigen Trigger, welche im Endeeffekt im Sekundentakt gestartet wird...


Ansonsten wäre es vermutlich einfacher, die Funktion umzubauen und nur einen Timer zu verwenden. Die Frage ist aber auch, ob die Menge an Log Meldungen wirklich zielführend ist.
Mich intressiert nur:

Code: Alles auswählen

sendBroadcastNotification
Die logInfo im war nur im ersten moment intressant, im Regelbetrieb benötige ich das nicht.
Gruss mad-mike

openHABian 4.2.2 auf Raspberry Pi 4 Mod. b (8GB) ;)

Benutzeravatar
udo1toni
Beiträge: 14829
Registriert: 11. Apr 2018 18:05
Answers: 232
Wohnort: Darmstadt

Re: SPH 4600 Growatt Modbus 485 -> openHAB

Beitrag von udo1toni »

Welche Persistences stehen denn zur Verfügung?

Im Grunde wäre ein anderer Ansatz sowohl für Licht also auch für die Wärmepumpe sinnvoller.
Eine Rule, welche den Sollzustand setzt:

Code: Alles auswählen

rule "PV_total changed"
when
    Item PV_total changed
then
    if(!(newState instanceof Number))
        return;
    val nCur = (newState as Number).intValue

    if(nCur <   750 ) BwwpSoll.postUpdate(OFF)
    if(nCur >  2000 ) BwwpSoll.postUpdate(ON)
    if(nCur >= 1000 && BwwpSoll.state == OFF) // Falls aus gefordert war, nun aber neutral
        BwwpSoll.postUpdate(NULL)

    if(nCur <   100 ) LichtSoll.postUpdate(ON)
    if(nCur >   300 ) LichtSoll.postUpdate(OFF)
end
Beachte, dass diese Rule die Zustände in Items speichert:

Code: Alles auswählen

Switch BwwpSoll  "Sollzustand Bwwp [%s]"
Switch LichtSoll "Sollzustand Licht[%s]"
und für das Item BwwpSoll kommt die Besonderheit des NULL-Zustands hinzu.

Für die Wärmepumpe geht es ja auch darum, ein Ausschalten zu verhindern, wenn der Grenzwert kurz unterschritten wird, aber anschließend wieder überschritten wird, ohne aber die Einschaltschwelle zu überschreiten. Deshalb muss hier so kompliziert der Sollzustand bestimmt werden.
Für das Licht dürfte es hingegen ausreichen, zwei Grenzwerte zu definieren. Liegt die Messung innerhalb der Grenzwerte, passiert nichts, liegt sie außerhalb, soll aus- bzw. eingeschaltet werden.
Die Schaltvorgänge kannst Du jeweils mit einer anderen Rule vornehmen.

Ich habe ja oben nach den verwendeten Persistences gefragt... bei rrd4j gibt es eine Einschränkung, die Funktion .previousState steht nicht zur Verfügung. Diese Funktion wäre hier aber extrem hilfreich:

Code: Alles auswählen

rule "BwwpSoll changed"
when
    Item BwwpSoll changed                                            // Freigabe BWWP
then
    tBwwp?.cancel                                                    // bestehende Timer entfernen
    if(newState == NULL)                                    // Muss Timer erzeugt werden?
        return;
    if(newState == Zigbee_Steckdose_1.state)
        return;

    val lNow = now.toEpochSecond                                                                   // aktueller Zeitstempel als Sekunden
    val lLastChange = Zigbee_Steckdose_1.prevoiusState(true,"influxdb").getTimestamp.toEpochSecond // letzte Änderung...
    val lChangedSince = lNow - lLastChange                                                         // Differenz
    var nSeconds = 3600 - lChangedSince                                                            // eine Stunde - Differenz
    if(nSeconds < 300) nSeconds = 300                                                              // falls kürzer als 5 Minuten, nimm 5 Minuten
    tBwwp = createTimer(now.plusSeconds(nSeconds), [|                                              // Timer anlegen, Start nach berechneter Zeit
        Zigbee_Steckdose_1.sendCommand(BwwpSollSoll.state.toString)
        sendBroadcastNotification("BWWP " + BwwpSollSoll.state.toString)                           // Gib Meldung aus
    ])
end
Die Rule triggert bei jeder Änderung des Sollzustands und entfernt einen eventuell laufenden Timer.
Ist der neue Sollzustand NULL, bricht die Rule ab. Entspricht der neue Sollzustand dem Istzustand, bricht die Rule ab.
Unterscheidet sich der Sollzustand vom Istzustand, ermittelt die Rule, ob die letzte Zustandsänderung weniger als 60 Minuten her ist. Wenn dieser Zeitpunkt mehr als 55 Minuten in der Vergangenheit liegt, wird ein Timer mit 50 Minuten Laufzeit gestartet, ansonsten wird der Timer entsprechend weiter in die Zukunft geschoben. Im Timer selbst wird der neue Sollzustand als Befehl gesendet.

Bei Nutzung von rrd4j funktioniert das leider nicht.
Das Licht ist dann natürlich eine getrennte Rule mit einem getrennten Timer.
openHAB4.2.2 stable in einem Debian-Container (bookworm) (Proxmox 8.2.8, LXC), mit openHABian eingerichtet

Antworten