Daikin - ESP32 Faikin Modul

@xudabit
Hab jetzt den ersten Tag im Thermostatmodus hinter mir und muß mich noch etwas an das Trendbild gewöhnen.
Entscheident für mich die Einstellung mit:
autot:21,6°C
autor:0,2
heatback: 3
heatover:-1
timenoflap:60
Econo: an
Bedarf:75%

Bis Mittags recht unbedeutend da der Raum nur aufgeheizt wird. Habe dann ja nochmal autot und autor verstellt (ca 14h) auf die oben genannten Werte.

Für mich pendelt die Leistung nun gemäß dem Über/Unterschwingen der Temperatur des Thermostatmodus.Das schaut für mich gut und besser als im normalen Automodus aus.

Die Stellgröße des Dakinsollwertes empfinde ich auch besser geregelt, brauch da aber noch Eindrücke um mir ein Bild zu machen. Egal wie, definitiv besser wie der Automodus und schon mal besten Dank!

Du kannst theoretisch auch mit heatover auf -2 runtergehen. Damit regelst du die Anlage nochmal etwas zurück, aber sie heizt weiter. Ich überlege noch wie ich das ggfs. sauber in meine Steuerung einbauen kann.

Ich würde mal "tempnoflap" auf 0 setzen. Du bewegst dich mit einem heatback von 3 gerade so an der Grenze zum Heizen. Daher ist hier sinnvoll, wenn das Faikin sofort nachregeln kann. Wenn das nicht passiert, kann es dir voraussichtlich schnell passieren, dass die Anlage kurz abschaltet.

Danke, was ca 16:41h der Fall war. probiere mal.

Mir wäre die Regelung immer noch viel zu "unruhig". Eigentlich könnte und sollte sie in etwa so aussehen:

Ich regele ausschließlich über Bedarfssteuerung und Soll-IG. Passend zu dem oben gezeigten Verlauf die Regelung der beiden Parameter während des Betrachtungszeitraums:

Das Problem beim Faikin-Adapter ist genauso wie bei der Originalsteuerung (wenn auch in abgeschwächter Form), dass es eine Universalregelung ist, die parametrisiert an die realen Bedürfnisse angenähert werden kann. Da sich die Regelung aber ausschließlich auf Anlagenparameter stützt, bleiben die räumlichen Gegebenheiten völlig außen vor.
Sobald die reale Raumtemperatur (an sinnvoller Stelle gemessen) mit in die Regelung einfließt, bekommt man einen Leistungsverlauf wie bei meiner Anlage.
Just my 2 cents.

Der Trend der Leistung sieht hier ruhiger aus durchaus.

Im Interessanten Zeitraum von 18h bis 21h ist dein Bedarf aber konstant. Entsprechend für mich interessant wie Du den Sollwert regelst. Das sieht für mich aber ehrlicherweise nach einer recht liniearen Reduktion innerhalb von 1h aus. Hieraus eine so schöne/konstante Leistungskurve zu erhalten, sehe ich aktuell auf Grundlage der Eingriffsmöglichkeiten in die Regelung als recht zufällig und nicht immer realisierbar an.
Bei mir sind diesselben Stellgrößen konstant/unverändert und die Daikin zappelt dennoch mehr rum. Das liegt für mich auch teils außerhalb des Beeinflußbaren.

Ich denke auch, dass die Nutzung eines externen Fühlers entscheidend ist. Ich werde trotzdem Faikins einsetzen, damit kann ich 1. sehr günstige Raumsensoren einsetzen und ins Netz bekommen, 2. für alle Innengeräte auf die Daikin Cloud verzichten und 3. bekomme ich eine schnellere Reaktion auf Änderungen der vom Innengerät gemessenen Temperatur, was für eine gute "externe" Regelung wichtig ist.

Nein, nicht mein Bedarf ist konstant sondern der Wert der Bedarfssteuerung ist konstant, weil die Parameter, die zur Justierung von Soll-IG und Bedarfssteuerung führen, keinen Anlass geben, die freigegebene maximale Leistung zu verändern.
Das ist ein riesiger Unterschied.

Die Steuerung von Soll-IG darf man nicht isoliert von Raum-Soll und Raum-Ist betrachten. Dazu kommt eine zeitliche Komponente, ausgedrückt in der Steigung der Kurve von Raum-Ist.

Nenn mir einen beliebigen Tag und ich liefere Dir die Leistungskurve. Da ist nichts zufällig. Natürlich kann ich bei bestimmten Witterungsbedingungen Abtauintervalle nicht verhindern. Diese Schwankungen sind dann aber by design.

Erstaunlicherweise lassen sich Daikins über das lokale Interface (dazu würde dann auch Faikin vermutlich gehören) sehr präzise auf eine gewünschte Raumtemperatur einregeln. Z.B. die zweite Anlage, die u.a. fürs Büro zuständig ist:


Kurz nach 12:00 Uhr kam das Schlafzimmer nach längerem Lüften dazu. Danach dauert es ein bisschen, bis sich die Anlage wieder ausschließlich aufs Büro eingeschwungen hat, läuft dann aber wieder konstant durch.

Dagegen spricht auch überhaupt nichts, zumal es neben den noch als Restbeständen verfügbaren original Daikin externen WLAN-Modulen die einzige Möglichkeit ist, aus der Cloud raus zu kommen. Wenn dann ein Raumtemperatursensor an geeigneter Stelle dazu kommt, sollte sich Faikin genauso einregeln lassen wie die lokalen APIs.

Wie sieht denn deine Steuerung dazu aus? Würde mich durchaus mal interessieren was du anders machst.

1 „Gefällt mir“

Prima, dann beschreibe doch bitte einmal konkret wie es funktioniert und stocher nicht nebulös in der Gegend rum, dass man den Eindruck gewinnen kann du fühlst Dich sofort in Deiner Lösung kritisiert.

Ich hatte nicht das Gefühl, dass er sich kritisiert gefühlt hatte.

Meiner Meinung nach aufgrund der unterschiedlichen Gegebenheiten wirklich schwierig eine ganz saubere Kurve hinzubekommen, aber wenn Jogobo da eine saubere Berechnung hat könnte ich das vllt. mal ausprobieren. Je nachdem wie komplex sich das hier einbauen lassen würde.

Welche Laus ist Dir denn über die Leber gelaufen?

rule "Temperaturpruefung"
when
  Time cron "30 0/5  * ? * MON-SUN"
then
  var deltaT05 = 10.0f as Number
  var deltaT10 = 0.0f as Number
  var diff = 0.0f
  var roomDiff = 0.0f
  var modeFaktor = 1f
  var limiter = 3f

  gRoomsWithDaikin.members.forEach[ GroupItem m |
    val gDaikin = m.members.findFirst[ d | d.name.startsWith("gDaikin") ] as GroupItem
    val klimaItem = gDaikin.members.findFirst[ k | k.name.endsWith("_Power") ] as SwitchItem

    if (klimaItem.state == ON) {
      deltaT05 = 10.0f as Number
      deltaT10 = 0.0f as Number

      var lastChange = (gDaikin.members.findFirst[ t |
                          t.name.endsWith("_LastChange")
                        ].state as DateTimeType).getZonedDateTime
      if (now.minusMinutes(5).isAfter(lastChange))
      {
        val modeItem = gDaikin.members.findFirst[ k |
                         k.name.endsWith("_Mode") ] as StringItem
        val fanItem = gDaikin.members.findFirst[ k |
                        k.name.endsWith("_Fan") ] as StringItem
        val aktTempItem = m.members.findFirst[ t |
                            t.name.endsWith("_ActualTemperature")
                          ] as NumberItem
        val setTempItem = m.members.findFirst[ t |
                            t.name.endsWith("_SetTemperature")
                          ] as NumberItem
        val setPointItem = gDaikin.members.findFirst[ t |
                             t.name.endsWith("_SetPoint")
                           ]
        val inTempItem = gDaikin.members.findFirst[ t |
                           t.name.endsWith("_IndoorTemperature")
                         ]
        val demandItem = gDaikin.members.findFirst[ d |
                           d.name.endsWith("MaxPower")
                         ]
        val dcItem =  gDaikin.members.findFirst[ d |
                        d.name.endsWith("DemandControlMode")
                      ]

        var setPoint = (setPointItem.state as Number).floatValue
        val inTemp = (inTempItem.state as Number).floatValue
        var demandPower = (if (demandItem.state == UNDEF) minPower
                           else (demandItem.state as Number).intValue)
        var deltaSecs = 0
        var tItem = aktTempItem.previousState()

        if (modeItem.state == "COLD") {
          modeFaktor = -1f
          limiter = -1f
        }
        else {
          modeFaktor = 1f
          limiter = 3f
        }

        do {
          deltaSecs = (now.toEpochSecond - ZonedDateTime.parse(tItem.timestamp.toString).toInstant.toEpochMilli / 1000 as Number).intValue + 1

          if (deltaSecs > 300 && deltaT05.intValue == 10) {
            deltaT05 = ((aktTempItem.deltaSince(now.minusSeconds(deltaSecs)) as Number).floatValue / deltaSecs * 300f * modeFaktor) as Number
          }

          tItem = aktTempItem.persistedState(now.minusSeconds(deltaSecs))
        } while (deltaSecs < 600)

        deltaT10 = ((aktTempItem.deltaSince(now.minusSeconds(deltaSecs)) as Number).floatValue / deltaSecs * 600f * modeFaktor) as Number
        roomDiff = ((aktTempItem.state as Number).floatValue -
                    (setTempItem.state as Number).floatValue) * modeFaktor

        // Raum-Soll erreicht
        if (roomDiff >= 0.0f) {
          if (deltaT05.floatValue >= 0.1f) {
            if (setPoint > inTemp) setPoint = inTemp
            else
              setPoint -= Math.round(10f * deltaT05.floatValue * modeFaktor)
          }
          else {
            if (deltaT05.floatValue > 0f && deltaT05.floatValue < 0.1f)
              setPoint -= 0.5f * modeFaktor
            else
              setPoint -= Math.round(10f * deltaT05.floatValue * modeFaktor) / 2f
          }

          // setPoint fürs Kühlen zu groß oder Heizen zu klein
          if (setPoint > inTemp - limiter && modeItem.state == "COLD" ||
              setPoint < inTemp - limiter && modeItem.state == "HEAT")
          {
            diff = Math.max(setPoint - (inTemp - limiter),
                            (inTemp - limiter) - setPoint)

            if (dcItem.state.toString.toUpperCase == "OFF" || demandPower <= minPower)
            {
              fanItem.sendCommand("SILENCE")
            }
            else {
              demandPower -= Math.round(10f * diff)
              demandPower = Math.max(minPower, demandPower)
            }

            setPoint = inTemp - limiter
          } // setPoint < minTemp ...
        } // roomDiff >= 0.0f

        // Kurz vor Raum-Soll
        if (roomDiff >= -0.5f && roomDiff < 0.0f) {
          if (fanItem.state == "SILENCE") {
            fanItem.sendCommand("AUTO")
          }

          if (deltaT05.floatValue > 0.1f)
            setPoint -= Math.round(20f * deltaT05.floatValue * modeFaktor)

          if (deltaT05.floatValue <= 0f)
            setPoint -= Math.round(roomDiff * 10f * modeFaktor)

          if (modeItem.state == "HEAT")
            setPoint = Math.max(inTemp - limiter + 0.5f, setPoint)

          if (setPoint > maxTemp && modeItem.state == "HEAT" ||
              setPoint < minTemp && modeItem.state == "COLD")
          {
            demandPower = Math.min(demandPower + 5, 95)

            setPoint = (if (modeItem.state == "HEAT") inTemp else minTemp)
          }
        } // roomDiff >= -0.5 && roomDiff < 0

        // Raum-Soll noch weit entfernt
        if (roomDiff < -0.5f) {
          if (fanItem.state == "SILENCE") {
            fanItem.sendCommand("AUTO")
          }

          if (deltaT05.floatValue < 0.1f)
            setPoint -= Math.round(roomDiff * 10f * modeFaktor) * 0.5f

          if (setPoint > maxTemp && modeItem.state == "HEAT" ||
              setPoint < minTemp && modeItem.state == "COLD")
          {
            demandPower += 5
            setPoint = (if (modeItem.state == "HEAT")
                          (inTemp + (maxTemp - inTemp) / 2f)
                        else minTemp)
          }
        } // roomDiff < -0.5

        demandPower = Math.min(95, Math.max(minPower, demandPower))
        if (modeItem.state == "HEAT")
          setPoint = Math.min(maxTemp, setPoint)
        if (modeItem.state == "COLD")
          setPoint = Math.max(minTemp, setPoint)

        setPoint = Math.round(setPoint * 2f) / 2f

        if (modeItem == "HEAT" && setPoint < inTemp &&
            (setPointItem.state as Number).floatValue - setPoint > 2f)
        {
          setPoint = (setPointItem.state as Number).floatValue - 2f
        }

        if (demandItem.state != UNDEF &&
            dcItem.state.toString.toUpperCase != "OFF")
        {
          if (demandPower != (demandItem.state as Number).intValue) {
            demandItem.sendCommand(demandPower)

            setPointItem.persist
            inTempItem.persist
            setTempItem.persist
          }
        }

        if (setPoint != (setPointItem.state as Number).floatValue) {
          setPointItem.sendCommand(setPoint)
          demandItem.persist
          inTempItem.persist
          setTempItem.persist
        }
      } // now.plusMinutes(5).isAfter(lastChange)
    } // klimaItem == ON
  ]
end

Ich stelle meine Raumthermostate abhängig von der AT automatisch auf "Heizen" bzw. "Kühlen" um. Deshalb ist die Regelung so ausgelegt, dass sie für beide Betriebsarten funktioniert.

1 „Gefällt mir“

Danke, schaue ich mir mal an. Mal schauen wann ich dazu komme, dann würde ich das in NodeRed einpflegen und mal ausprobieren.

@jogobo Ich finds total super, dass Du das hier teilst. Ich sehe, Du hast inzwischen in der Rule ausschließlich auf ein festes Zeitraster gewechselt. Heißt das, dass Du nicht mehr (wie in der früheren Version) synchron mit der "Indoortemperature" (interne Temperatur des Innengeräts) den Setpoint verstellst? Oder passiert noch etwas außerhalb dieses Codes, wenn sich die Indoortemperature verändert?

Das, was ich hier gepostet habe, ist nur die periodische Prüfung und Regelung auf Raum-Soll. Dieser Teil ist ja auch der "schwierige". Die Ereignisprozeduren bei manueller Änderung von Raum-Soll bzw. geänderten Messwerten für Raum-Ist, IG-Ist, usw. fehlen hier noch.
Eine der wichtigsten Ereignisprozeduren ist immer noch die Reaktion auf IG-Ist, um bei erreichtem Raum-Soll die Differenz zwischen IG-Ist und IG-Soll unterhalb der Ausschaltgrenze zu halten. Das führt am Ende auch zu dieser sehr konstanten Leistungsaufnahme, die ich mittlerweile erreicht habe.

Ich habe für jeden Raum eine Sensoren-Aktoren-Gruppe erstellt, über die ich raumweise Sensoränderungen mitbekomme und die Aktoren in der Ereignisprozedur entsprechend nachjustieren kann. Dazu noch eine Startroutine bei Einschalten eines IGs und eine Synchronisation der Bedarfssteuerung aller IGs an einem AG.

Danke fürs Teilen. Da mein Automatisierungssystem nicht besonders auf Verfügbarkeit ausgelegt ist, ist eigentlich nicht mein Plan solche Funktionalitäten dort gesammelt zu hinterlegen. Aber zum verstehen wie es gehen kann, sehr schön.

D.h. hier führst Du wie gehabt den SetPoint, so wie er in der Temperaturprüfung ermittelt wurde, mit der Indoortemperature nach?

Genau so wie damals. Der Code ist mittlerweile nur ein bisschen aufgeräumter und universeller.

Was ist minTemp und maxTemp? Ich denke, dass ich es gleich größtenteils verstanden habe.

Ich bin mir allerdings noch nicht sicher, ob ich die Steuerung so haben wollen würde. Mein Gedanke geht hierbei auch immer dahin, dass es vllt. mal Verbindungsprobleme oder ein Problem mit dem Server gibt. Wenn deine Berechnung bspw. für 1 Stunde nicht nachregelt, dann bekommst du ggfs. ein Problem oder sehe ich das falsch? Optimal wäre hier eigtl., wenn diese Regelung auf dem Faikin laufen würde.

Das sind die Ober- und Untergrenze von IG-Soll.

Das Problem habe ich nicht, weil ich WLAN-Adapter der B-Serie habe und deshalb lokal ohne Cloud arbeite.

Deswegen habe ich mich hier eingeklinkt. Das sollte mit Faikin problemlos zum Laufen zu kriegen sein.

Ich beziehe mich eher auf Verbindungsprobleme mit dem WLAN oder ggfs. auch ein Ausfall des Servers auf dem das ganze läuft. Ist bei selfhosted aber immer so eine Sache.

Um die Regelung auf das Faikin zu bringen habe ich aktuell leider nicht die Zeit um mich in die Thematik reinzuarbeiten. Ich werde es voraussichtlich mal in NodeRed bringen. Interessiert mich schon wie sich das im Vergleich auswirkt.