Daikin - ESP32 Faikout Modul

@dieter_p ganz einfach. Die Abschaltungen kommen, da die Raum Temperatur erreicht ist. Weiter runter als 40% Bedarf geht es nicht.

Für diese Temperatur ist der Raum zu klein oder die Inneneinheit zu groß.

1 „Gefällt mir“

Genau so habe ich das damals auch gesehen. Ich habe 3 Logo 8 und den iobroker oben drauf.

Ich habe aber als 24V System. Fand ich besser. Ob ein Schalter in der Wand mit 230V oder mit 24V arbeitet, spielt doch keine Rolle

Danke. Verstanden da Du als Stellgröße nur den Bedarf nutzt.

Ich vermute hier ist noch Potential da der Kompressor noch nicht am Minimum ist.
Es gibt hier im Beitrag irgendwo ein Beispiel wo jemand bei erreichen der Solltemperatur sowohl am Bedarf als auch am Sollwert regelt. Das ergab eine recht perfekte Regelung, allerdings durchaus nicht inkomplex. Daher noch nicht probiert.

Auf IOBroker Javascript transferiert dürfte es etwa so funktionieren aber kann gerade leider nicht testen :upside_down_face:

/***************************************************
 * CONFIG – bitte anpassen!
 ***************************************************/

// ioBroker-Datenpunkte (nur lesen!)
const DP = {
    power:               "daikin.0.wohnzimmer.power",
    mode:                "daikin.0.wohnzimmer.mode",
    fan:                 "daikin.0.wohnzimmer.fan",
    actualTemp:          "daikin.0.wohnzimmer.actualTemp",
    setTemp:             "daikin.0.wohnzimmer.setTemp",
    indoorTemp:          "daikin.0.wohnzimmer.indoorTemp",
    setPoint:            "daikin.0.wohnzimmer.setPoint",
    maxPower:            "daikin.0.wohnzimmer.maxPower",
    dcMode:              "daikin.0.wohnzimmer.dcMode",
    lastChange:          "daikin.0.wohnzimmer.lastChange"
};

// MQTT Topics für Kommandos
const MQTT = {
    setPoint:            "daikin/wohnzimmer/setPoint",
    fan:                 "daikin/wohnzimmer/fan",
    demandPower:         "daikin/wohnzimmer/demandPower"
};

// Parameter
const minPower = 10;
const maxTemp  = 26;
const minTemp  = 18;

/***************************************************
 * AB HIER NICHTS MEHR ÄNDERN
 ***************************************************/

schedule("30 */5 * * * *", async () => {

    log("Starte Temperaturregelung Wohnzimmer…");

    // Wenn Klimagerät aus → nichts tun
    if (getState(DP.power).val !== true) {
        log("Klimagerät ist aus – abbrechen.", "info");
        return;
    }

    const nowTs = Date.now();

    // Werte lesen
    const mode         = getState(DP.mode).val;            // "HEAT" / "COLD"
    const fan          = getState(DP.fan).val;
    const actualTemp   = parseFloat(getState(DP.actualTemp).val);
    const setTemp      = parseFloat(getState(DP.setTemp).val);
    const indoorTemp   = parseFloat(getState(DP.indoorTemp).val);
    let setPoint       = parseFloat(getState(DP.setPoint).val);
    let demandPower    = getState(DP.maxPower).val;
    const dcMode       = getState(DP.dcMode).val;

    // Prüfen ob Änderung vor 5 Minuten
    const lastChangeTs = new Date(getState(DP.lastChange).val).getTime();
    if (nowTs - lastChangeTs < 5 * 60 * 1000) {
        log("Letzte Änderung <5 Minuten – abbrechen.");
        return;
    }

    /***************************************************
     * Temperaturverlauf 5 Min & 10 Min (deltaT05/10)
     ***************************************************/
    async function getDelta(seconds) {
        // Hole den Wert von vor X Sekunden (History erforderlich)
        const hist = await getHistory(DP.actualTemp, {
            start: nowTs - seconds * 1000,
            end:   nowTs,
            aggregate: "none",
            count: 2
        });

        if (!hist || hist.length < 1) return 0;

        const past = parseFloat(hist[0].val);
        return ((actualTemp - past) / seconds) * (seconds === 300 ? 300 : 600);
    }

    const deltaT05 = (await getDelta(300)) || 0;
    const deltaT10 = (await getDelta(600)) || 0;

    // Raum-Abweichung vom Soll
    const modeFaktor = mode === "COLD" ? -1 : 1;
    const limiter    = mode === "COLD" ? -1 : 3;
    const roomDiff   = (actualTemp - setTemp) * modeFaktor;

    /***************************************************
     * Steuerlogik – exakt aus deiner OpenHAB-Regel
     ***************************************************/

    // Raum-Soll erreicht
    if (roomDiff >= 0) {

        if (deltaT05 >= 0.1) {
            if (setPoint > indoorTemp) setPoint = indoorTemp;
            else setPoint -= Math.round(deltaT05 * 10 * modeFaktor);
        } else {
            if (deltaT05 > 0 && deltaT05 < 0.1)
                setPoint -= 0.5 * modeFaktor;
            else
                setPoint -= Math.round(deltaT05 * 10 * modeFaktor) / 2;
        }

        if ((setPoint > indoorTemp - limiter && mode === "COLD") ||
            (setPoint < indoorTemp - limiter && mode === "HEAT")) {

            const diff = Math.abs(setPoint - (indoorTemp - limiter));

            if (dcMode === "OFF" || demandPower <= minPower) {
                mqttSend(MQTT.fan, "SILENCE");
            } else {
                demandPower -= Math.round(diff * 10);
                demandPower = Math.max(minPower, demandPower);
            }

            setPoint = indoorTemp - limiter;
        }
    }

    // Kurz vor Raum-Soll
    if (roomDiff >= -0.5 && roomDiff < 0) {

        if (fan === "SILENCE") mqttSend(MQTT.fan, "AUTO");

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

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

        if (mode === "HEAT")
            setPoint = Math.max(indoorTemp - limiter + 0.5, setPoint);

        if ((mode === "HEAT" && setPoint > maxTemp) ||
            (mode === "COLD" && setPoint < minTemp)) {

            demandPower = Math.min(demandPower + 5, 95);
            setPoint = mode === "HEAT" ? indoorTemp : minTemp;
        }
    }

    // Raum-Soll weit entfernt
    if (roomDiff < -0.5) {

        if (fan === "SILENCE") mqttSend(MQTT.fan, "AUTO");

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

        if ((mode === "HEAT" && setPoint > maxTemp) ||
            (mode === "COLD" && setPoint < minTemp)) {

            demandPower += 5;
            setPoint = mode === "HEAT"
                ? indoorTemp + (maxTemp - indoorTemp) / 2
                : minTemp;
        }
    }

    /***************************************************
     * Grenzen einhalten & runden
     ***************************************************/
    setPoint = Math.min(maxTemp, Math.max(minTemp, setPoint));
    setPoint = Math.round(setPoint * 2) / 2;
    demandPower = Math.max(minPower, Math.min(95, demandPower));

    /***************************************************
     * MQTT Kommandos senden
     ***************************************************/
    mqttSend(MQTT.setPoint, setPoint);
    mqttSend(MQTT.demandPower, demandPower);

    log(`Neuer SetPoint: ${setPoint}, DemandPower: ${demandPower}`);
});

/***************************************************
 * MQTT Sendehilfe
 ***************************************************/
function mqttSend(topic, payload) {
    setState("mqtt.0." + topic, payload, true);
    log(`MQTT → ${topic} = ${payload}`);
}

Prinzipiell ist das natürlich richtig, kannst auch mit 5V Steuersignal arbeiten dann kann der Arduino gleich mit horchen :grinning_face:

Ich hatte nicht unbegrenzt Platz in den Feldverteilern, da kam es mir ganz gelegen auf ein zusätzliches 24V Netzteil für die Hutschiene verzichten zu können. Zudem lassen sich zumindest alle Lichtkreise (mit entsprechender Vorsicherung) direkt über die in der Logo integrierten Relais schalten und ich muss nicht mit lauter Koppelrelais rumhantieren die auch wieder Platz auf der Hutschiene brauchen.

Es gibt da für alles ein für und wider - ich finds jedenfalls cool das du auch etablierte, industrietaugliche Steuerungen einsetzt :+1:

1 „Gefällt mir“

@dieter_p Klingt gut und könnte funktionieren, nur grob überflogen. Aber ich habe nur in der Übergangszeit Abschaltungen. Von daher nicht die höchste Priorität.

Bin mit dem Thermostatmodus auch ganz zufrieden, aber Optimierungen gehen ja immer :wink:

Edit: Habs die transferierte Version auf JavaScript nun für den IOBroker in Funktion bekommen:

/************************************************************
 * DAIKIN TEMPERATUR- UND LEISTUNGSREGELUNG (1. OG)
 * ----------------------------------------------------------
 * Dieses Skript steuert eine Daikin-Klimaanlage über MQTT.
 * Alle 5 Minuten werden SetPoint (Zieltemperatur der Daikin)
 * und DemandPower (Leistungsanforderung 30–100 %) automatisch 
 * berechnet und an die Daikin-Anlage gesendet.
 *
 * Das Skript nutzt:
 *  - Raumtemperatur (Externer Sensor)
 *  - Solltemperatur des Nutzers
 *  - Temperaturtrend der letzten 10 Minuten
 *  - Innentemperatur der Daikin
 *  - aktuellen Modus (Heizen/Kühlen)
 *
 * Dadurch arbeitet die Anlage effizienter, taktet weniger
 * und hält die Temperatur stabiler.
 ************************************************************/


/************************************************************
 * 1) KONFIGURATION – HIER AN DIE EIGENE INSTALLATION ANPASSEN
 ************************************************************/

const DP = {
    // Schaltzustand der Klimaanlage (true = AN)
    power:      "0_userdata.0.Daikin1OG.power",

    // Betriebsmodus: "H" = Heat, "C" = Cool, "A" = Auto, "F" = Fan, "D" = Dry
    mode:       "0_userdata.0.Daikin1OG.Mode",

    // Lüftermodus: "A" = Auto, "Q" = Quiet
    fan:        "0_userdata.0.Daikin1OG.fan",

    // aktuelle Raumtemperatur
    actualTemp: "0_userdata.0.Daikin1OG.Raumtemperatur",

    // Solltemperatur aus dem Smart Home (Ziel des Nutzers)
    setTemp:    "0_userdata.0.Daikin1OG.Sollwert",

    // Innentemperatur des Daikin-Geräts (Sensor in der Einheit)
    indoorTemp: "0_userdata.0.Daikin1OG.Innentemperatur",

    // aktueller SetPoint, der an Daikin gesendet wurde
    setPoint:   "0_userdata.0.Daikin1OG.SollwertDaikin",

    // aktuelle Leistungsanforderung (wird vom Skript überschrieben)
    maxPower:   "0_userdata.0.Daikin1OG.demand",

    // ob DynamicControl (stufenlose Regelung) aktiv ist
    dcMode:     "ON"
};

// MQTT Topics der Daikin-Steuerung
// HINWEIS: KEIN mqtt.0 VORANSTELLEN!
const MQTT = {
    setPoint:    "command/9C139EEF6D28/temp",
    fan:         "command/9C139EEF6D28/fan",
    demandPower: "command/9C139EEF6D28/demand"
};

// WICHTIG: Daikin akzeptiert NUR 30–100 %
const minPower = 30;        // minimale Leistungsanforderung
const maxPowerLimit = 100;  // maximale Leistungsanforderung

// Temperaturgrenzen der Daikin
const maxTemp  = 26;
const minTemp  = 18;


/************************************************************
 * 2) TEMPERATUR-HISTORIE
 * ----------------------------------------------------------
 * Hier werden 10 Minuten Temperaturverlauf gespeichert.
 * Damit erkennt die Logik Trends:
 *  - wird es wärmer?
 *  - wird es kälter?
 *  - wie stark / wie schnell?
 *
 * Das ist die Grundlage für die stufenlose Regelung.
 ************************************************************/

let tempHistory = [];
const defaultTemp = 18;

/**
 * Speichert die aktuelle Raumtemperatur mit Timestamp.
 */
function addTemp(value) {
    const ts = Date.now();
    tempHistory.push({ timestamp: ts, value });

    // nur die letzten 10 Minuten behalten
    tempHistory = tempHistory.filter(
        e => e.timestamp >= ts - 10 * 60 * 1000
    );
}

/**
 * Berechnet Delta der Temperaturänderung über X Sekunden.
 * Rückgabewert = °C pro Sekunde.
 */
function getDelta(seconds) {
    const now = Date.now();
    const past = tempHistory.find(e => e.timestamp <= now - seconds * 1000);
    if (!past) return 0;

    const last = tempHistory[tempHistory.length - 1];
    return (last.value - past.value) / seconds;
}


/************************************************************
 * 3) MQTT SENDEN
 * ----------------------------------------------------------
 * Sendet korrekt über "sendMessage2Client" an MQTT.
 ************************************************************/

function mqttSend(topic, payload) {
    const message = String(payload);

    sendTo("mqtt.0", "sendMessage2Client", {
        topic,
        message,
        retain: false
    });

    log(`MQTT → ${topic}: ${message}`, "info");
}


/************************************************************
 * 4) HAUPTLOGIK — alle 5 Minuten
 * ----------------------------------------------------------
 * Ablauf:
 *   1. Werte einlesen
 *   2. Temperaturverlauf analysieren
 *   3. Regelungslogik berechnet SetPoint & demandPower
 *   4. Werte begrenzen
 *   5. Per MQTT senden
 ************************************************************/

schedule("0 */5 * * * *", () => {

    log("Starte Temperaturregelung 1.OG");

    // Wenn Gerät ausgeschaltet ist → nicht regeln
    if (getState(DP.power).val !== true) {
        log("Klimagerät AUS – kein MQTT senden", "info");
        return;
    }

    /********************************************************
     * 4.1 Werte aus ioBroker auslesen
     ********************************************************/
    const mode       = getState(DP.mode).val || "H";
    const fan        = getState(DP.fan).val  || "A";
    const actualTemp = parseFloat(getState(DP.actualTemp).val) || defaultTemp;
    const setTemp    = parseFloat(getState(DP.setTemp).val)    || defaultTemp;
    const indoorTemp = parseFloat(getState(DP.indoorTemp).val) || defaultTemp;
    let setPoint     = parseFloat(getState(DP.setPoint).val)   || defaultTemp;
    let demandPower  = parseFloat(getState(DP.maxPower).val)   || minPower;
    const dcMode     = DP.dcMode;

    /********************************************************
     * 4.2 Modus-Faktor (Vorzeichen der Regelung)
     *    Heizen → +1
     *    Kühlen → -1
     ********************************************************/
    let modeFaktor = 1;
    switch (mode) {
        case "C":
        case "D":
            modeFaktor = -1;
            break;

        case "H":
        case "A":
            modeFaktor = 1;
            break;

        case "F": // Nur Lüften
            log("Lüftermodus – Logik übersprungen.", "info");
            return;
    }

    /********************************************************
     * 4.3 Temperaturhistorie erweitern
     ********************************************************/
    addTemp(actualTemp);

    const deltaT05 = getDelta(300);  // Änderung über 5 min
    const deltaT10 = getDelta(600);  // Änderung über 10 min

    /********************************************************
     * 4.4 Berechnung Temperaturabweichung zum Sollwert
     ********************************************************/
    const limiter  = (mode === "C" || mode === "D") ? -1 : 3;
    const roomDiff = (actualTemp - setTemp) * modeFaktor;


    /********************************************************
     * 4.5 REGELALGORITHMUS
     * ------------------------------------------------------
     * Dieser Teil bestimmt:
     *  - neuen SetPoint der Daikin
     *  - Leistungsanforderung (demandPower)
     *
     * Der Code ist unverändert — nur Kommentare wurden
     * umfangreich ergänzt.
     ********************************************************/

    if (roomDiff >= 0) {
        // Raum ist "über Soll"

        if (deltaT05 >= 0.1) {
            // starker Temperaturanstieg → SetPoint stärker absenken
            if (setPoint > indoorTemp) setPoint = indoorTemp;
            else setPoint -= Math.round(deltaT05 * 10 * modeFaktor);

        } else {
            // schwacher Trend
            if (deltaT05 > 0 && deltaT05 < 0.1)
                setPoint -= 0.5 * modeFaktor;
            else
                setPoint -= Math.round(deltaT05 * 10 * modeFaktor) / 2;
        }

        // SetPoint darf Grenzwert nicht überschreiten
        if ((setPoint > indoorTemp - limiter && modeFaktor === -1) ||
            (setPoint < indoorTemp - limiter && modeFaktor === 1)) {

            const diff = Math.abs(setPoint - (indoorTemp - limiter));

            // falls DynamicControl off oder Power am Minimum → Lüfter reduzieren
            if (dcMode === "OFF" || demandPower <= minPower) {
                mqttSend(MQTT.fan, "Q");
            } else {
                // Leistung reduzieren
                demandPower -= Math.round(diff * 10);
                demandPower = Math.max(minPower, demandPower);
            }

            setPoint = indoorTemp - limiter;
        }
    }

    if (roomDiff >= -0.5 && roomDiff < 0) {
        // knapp unter Soll (Feinregelung)

        if (fan === "Q") mqttSend(MQTT.fan, "A");

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

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

        if (modeFaktor === 1)
            setPoint = Math.max(indoorTemp - limiter + 0.5, setPoint);

        // Temperaturgrenzen beachten
        if ((mode === "H" && setPoint > maxTemp) ||
            ((mode === "C" || mode === "D") && setPoint < minTemp)) {

            demandPower = Math.min(demandPower + 5, maxPowerLimit);
            setPoint = (modeFaktor === 1) ? indoorTemp : minTemp;
        }
    }

    if (roomDiff < -0.5) {
        // deutlich unter Soll → heizen/kühlen stärker

        if (fan === "Q") mqttSend(MQTT.fan, "A");

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

        if ((mode === "H" && setPoint > maxTemp) ||
            ((mode === "C" || mode === "D") && setPoint < minTemp)) {

            demandPower = Math.min(maxPowerLimit, demandPower + 5);

            setPoint = (modeFaktor === 1)
                ? indoorTemp + (maxTemp - indoorTemp) / 2
                : minTemp;
        }
    }

    /********************************************************
     * 4.6 Werte begrenzen und auf 0.5°C runden
     ********************************************************/
    setPoint    = Math.min(maxTemp, Math.max(minTemp, setPoint));
    setPoint    = Math.round(setPoint * 2) / 2;

    demandPower = Math.max(minPower, Math.min(maxPowerLimit, demandPower));


    /********************************************************
     * 4.7 Werte per MQTT an die Daikin senden
     ********************************************************/
    mqttSend(MQTT.setPoint, setPoint);
    mqttSend(MQTT.demandPower, demandPower);

    log(`Final → SetPoint: ${setPoint}, Demand: ${demandPower}`, "info");
});

Hab den Faden mal umgetauft, nachdem es mit dem Namen Faikin wohl andernorts Probleme gab …

2 „Gefällt mir“

Sicher ist sicher, keine Frage. Aber ich denke es ging um einen kommerziellen Vertrieb auf Amazon und weniger um einen Austausch von Klimaanlagen-Kunden in einem Forum.

Korrekt. Würde jetzt auch nicht erwarten, daß Foren da belangt werden. Wenn es deswegen jetzt entsprechend Faikout Modul genannt wird, ist es denke ich nicht verkehrt auch die Titel hier entsprechend dazu konsistent zu machen.

1 „Gefällt mir“

Hast Du auch schon Erfahrungswerte über das Regelverhalten? Würde mich interessieren, in wie weit sich mein Ansatz auf andere Gegebenheiten übertragen lässt.

Hab es nur sehr rudimentär in einer Aufheizphase und in der Nähe des Sollwertes mal getestet. Sah ok aus, aber für ein Fazit noch zu früh. Da ich gerade mangels PV-Strom eher möglichst mit Verbrauch spare, habe ich noch keinen ganzen Tag getestet.

Edit: Als Eindruck und auf 1 Minute Regelzyklus reduziert.

Start bei 20,3°C Raumtemperatur (Dunkelgrün) Sollwert 21,5°C (blau)
Econo und Vertikal Swing ist aktiv

Die Regelung fährt primär den Bedarf (hellblau) hoch und weniger stark den SetPoint der Temperatur (braun)

Ist der Sollwert erreicht, fährt erst der SetPoint der Temperatur runter dann der Bedarf.

Wie es sich Einschwingt noch zu beobachten:

Edit2:
Etwas größere Zeitbereich und zwischenzeitlich 3 Minütiges Regelintervall reduziert.

Die Bedarfssteuerung ist definitiv "nachgelagert". Unschön empfinde ich, dass der Raum bei Sollwert 21,5°C bis auf 22.5°C überheizt wird und bis dahin nur schrittweise der Temperatursetpoint reduziert wird. Erst bei nahezu Setpoint = Sollwert wird auch der Bedarf reduziert. Aktuell aus meiner Sicht, kann man damit arbeiten und erzielt leichte Tendenz zum Komfort statt Sparsamer Fahrweise.
Ich werde mal zum Testen den Bedarf zu limitieren, vielleicht bei 80% und schaue mal wie sich das verhält.

Die Regelung der Bedarfsteuerung nochmal angepasst und allgemein die Anlage für den optimalen Betriebspunkt bis 60% Leistung durch den EconoModus begrenzt aber manuell abschaltbar.

Morgens zum Aufheizen noch Vertikal Swing bis zur Solltemperatur oder max 1h aktiviert.

/***************************************************
 * CONFIG – Gerätekonfiguration und Datenpunkte
 ***************************************************/
const DP = {
    power:      "0_userdata.0.DaikinWohnzimmer.power",   // Einschalten / Ausschalten (interner Zustand)
    mode:       "0_userdata.0.DaikinWohnzimmer.Mode",    // Betriebsmodus: H/C/A/F
    fan:        "0_userdata.0.DaikinWohnzimmer.fan",     // Lüftermodus (A/Q/…)
    actualTemp: "alias.0.WohnzimmerEnv.ACTUAL",          // Raumtemperatur
    setTemp:    "0_userdata.0.DaikinWohnzimmer.Sollwert",// Vom Benutzer gewünschte Temperatur
    indoorTemp: "0_userdata.0.DaikinWohnzimmer.Innentemperatur", // Innentemperatur der Daikin
    setPoint:   "0_userdata.0.DaikinWohnzimmer.SollwertDaikin",  // Interner Sollpunkt der Anlage
    maxPower:   "0_userdata.0.DaikinWohnzimmer.demand",  // Leistungsanforderung 0–100%
    econo:      "0_userdata.0.DaikinWohnzimmer.econo",   // ECO-Modus AN/AUS
    swingv:     "0_userdata.0.DaikinWohnzimmer.swingv",  // Vertikaler Swing
    dcMode:     "ON"                                     // Direkte Steuerung aktiv/inaktiv
};

/***************************************************
 * MQTT Topics
 ***************************************************/
const MQTT = {
    setPoint:    "command/F0F5BD731830/temp",     // Solltemperatur
    fan:         "command/F0F5BD731830/fan",      // Lüftermodus
    demandPower: "command/F0F5BD731830/demand",   // Leistungsanforderung
    econo:       "command/F0F5BD731830/econo",    // ECO-Modus
    swingv:      "command/F0F5BD731830/control",  // Steuerkanal für Swing
    power:       "command/F0F5BD731830/control"   // Steuerkanal für Power { power: true/false }
};

const mqttAdapter = "mqtt.0"; // MQTT Adapter-Instanz in ioBroker

/***************************************************
 * Betriebsgrenzen
 ***************************************************/
const minPower = 30;       // Minimale Leistungsanforderung
const maxTemp  = 26;       // Maximal erlaubte Temperatur
const minTemp  = 18;       // Minimal erlaubte Temperatur
const optimalMin = 35;     // Optimale Leistungsuntergrenze
const optimalMax = 55;     // Optimale Leistungsobergrenze
const optimalMaxEcono = 90;// Obergrenze im ECO-Modus

/***************************************************
 * ECO-Modus-Konfiguration
 ***************************************************/
const econoAuto = 0;       // 0 = ECO dauerhaft aktiv halten

/***************************************************
 * Swing-Automatik
 ***************************************************/
const swingAuto = true;    // Swing aktivieren bei Auto-Start

/***************************************************
 * Zeitsteuerung – Start/Stopp-Zeiten pro Wochentag
 ***************************************************/
const scheduleConfig = {
    start: ["10:00", "10:00", "08:30", "10:00", "10:00", "10:00", "10:00"], // SO–SA
    stop:  ["22:00", "22:00", "22:00", "22:00", "22:00", "23:00", "23:00"]
};

/***************************************************
 * Temperaturverlauf zur Berechnung der Änderungsrate
 ***************************************************/
let tempHistory = [];
const defaultTemp = 18;

/**
 * Temperaturwert in Verlauf eintragen
 */
function addTemp(value) {
    const ts = Date.now();
    tempHistory.push({ timestamp: ts, value });
    // Nur die letzten 10 Minuten behalten
    tempHistory = tempHistory.filter(e => e.timestamp >= ts - 10 * 60 * 1000);
}

/**
 * Temperaturänderung pro Zeitraum (Sekunden)
 */
function getDelta(seconds) {
    const now = Date.now();
    const past = tempHistory.find(e => e.timestamp <= now - seconds * 1000);
    if (!past) return 0;
    const last = tempHistory[tempHistory.length - 1];
    return (last.value - past.value) / seconds;
}

/***************************************************
 * MQTT-Sende-Funktion
 ***************************************************/
function mqttSend(topic, payload) {
    const message = String(payload);
    sendTo(mqttAdapter, "sendMessage2Client", {
        topic,
        message,
        retain: false
    });
    log(`MQTT → ${topic}: ${message}`, "info");
}

/***************************************************
 * Power-Steuerung für Daikin
 ***************************************************/
function setPower(state) {
    setState(DP.power, state, false); // internen Zustand setzen
    const payload = JSON.stringify({ power: state });
    mqttSend(MQTT.power, payload);    // MQTT an Daikin senden
    log(`MQTT POWER gesendet → ${payload}`, "info");
}

/***************************************************
 * ECO-Modus sicherstellen
 ***************************************************/
function checkEcono() {
    const econoVal = getState(DP.econo).val === true;
    if (econoAuto === 0 && !econoVal) {
        // ECO im System aktivieren
        setState(DP.econo, true, false);
        // ECO an Daikin senden
        mqttSend(MQTT.econo, "1");
        log("ECO-Modus dauerhaft aktiviert", "info");
    }
}

/***************************************************
 * Swing-Automatik
 ***************************************************/
let swingTimer = null;

/**
 * Swing beim automatischen Start aktivieren
 */
function activateSwingAuto() {
    const swingState = getState(DP.swingv).val === true;
    if (!swingState) {
        mqttSend(MQTT.swingv, JSON.stringify({ swingv: true }));
        log("Swing aktiviert (Auto-Start)", "info");

        if (swingTimer) clearTimeout(swingTimer);
        swingTimer = setTimeout(deactivateSwing, 60 * 60 * 1000); // max. 1 Stunde
    }
}

/**
 * Swing wieder deaktivieren
 */
function deactivateSwing() {
    const swingState = getState(DP.swingv).val === true;
    if (swingState) {
        mqttSend(MQTT.swingv, JSON.stringify({ swingv: false }));
        log("Swing automatisch deaktiviert", "info");
    }
    swingTimer = null;
}

/***************************************************
 * Zeitsteuerung: Start/Stopp (robust)
 ***************************************************/
let lastStart = ""; // Datum, an dem zuletzt gestartet wurde
let lastStop  = ""; // Datum, an dem zuletzt gestoppt wurde

function checkSchedule() {
    const now = new Date();
    const weekday = now.getDay();                      // 0=SO … 6=SA
    const currentHM = now.toTimeString().slice(0, 5);  // hh:mm
    const today = now.toDateString();

    const startTime = scheduleConfig.start[weekday];
    const stopTime  = scheduleConfig.stop[weekday];

    /**************** START ****************/
    if (currentHM >= startTime && lastStart !== today) {
        setPower(true);
        lastStart = today;
        log("Automatischer Start ausgeführt", "info");
        if (swingAuto) activateSwingAuto();
    }

    /**************** STOP ****************/
    if (currentHM >= stopTime && lastStop !== today) {
        setPower(false);
        lastStop = today;
        log("Automatischer Stopp ausgeführt", "info");
        if (swingTimer) deactivateSwing();
    }
}

/***************************************************
 * Schedule jede Minute – prüft NUR Start/Stopp
 ***************************************************/
schedule("* * * * *", checkSchedule);

/***************************************************
 * HAUPT-REGELLOGIK – alle 3 Minuten
 ***************************************************/
schedule("0 */3 * * * *", () => {
    log("Starte Temperaturregelung Wohnzimmer");

    // ECO prüfen
    checkEcono();

    // Wenn Anlage (gewollt) AUS → keine Regelung
    if (getState(DP.power).val !== true) {
        log("Anlage ist AUS – Regelung übersprungen", "info");
        return;
    }

    /***************************************************
     * Werte einlesen
     ***************************************************/
    const mode       = getState(DP.mode).val || "H";
    const fan        = getState(DP.fan).val  || "A";
    const actualTemp = parseFloat(getState(DP.actualTemp).val) || defaultTemp;
    const setTemp    = parseFloat(getState(DP.setTemp).val)    || defaultTemp;
    const indoorTemp = parseFloat(getState(DP.indoorTemp).val) || defaultTemp;
    let setPoint     = parseFloat(getState(DP.setPoint).val)   || defaultTemp;
    let demandPower  = parseFloat(getState(DP.maxPower).val)   || 50;
    const econoState = getState(DP.econo).val === true;

    /***************************************************
     * Modusrichtung H(+)/C(-)
     ***************************************************/
    let modeFaktor = 1;
    switch (mode) {
        case "C": case "D": modeFaktor = -1; break; // Kühlen
        case "H": case "A": modeFaktor = 1;  break; // Heizen/Auto
        case "F": // Nur Lüften → keine Regelung
            log("Nur-Lüften-Modus – Regelung übersprungen", "info");
            return;
    }

    /***************************************************
     * Temperaturverlauf analysieren
     ***************************************************/
    addTemp(actualTemp);
    const deltaT05 = getDelta(300); // Veränderung in 5 Minuten
    const limiter  = (modeFaktor === -1 ? -1 : 3); // interne Begrenzung
    const roomDiff = (actualTemp - setTemp) * modeFaktor;

    /***************************************************
     * REGELLOGIK – reduziert / erhöht Sollwert & Leistung
     ***************************************************/

    /* --- Bereich: zu warm/zu kalt je nach Modus --- */
    if (roomDiff >= 0) {
        if (deltaT05 >= 0.1) {
            if (setPoint > indoorTemp) setPoint = indoorTemp;
            else setPoint -= Math.round(deltaT05 * 10 * modeFaktor);
        } else {
            if (deltaT05 > 0 && deltaT05 < 0.1)
                setPoint -= 0.5 * modeFaktor;
            else
                setPoint -= Math.round(deltaT05 * 10 * modeFaktor) / 2;
        }

        // Hard-Limiter
        if ((setPoint > indoorTemp - limiter && modeFaktor === -1) ||
            (setPoint < indoorTemp - limiter && modeFaktor === 1)) {

            const diff = Math.abs(setPoint - (indoorTemp - limiter));

            // Fan reduzieren bei niedriger Power
            if (demandPower <= minPower)
                mqttSend(MQTT.fan, "Q");
            else {
                demandPower -= Math.round(diff * 10);
                demandPower = Math.max(minPower, demandPower);
            }

            setPoint = indoorTemp - limiter;
        }
    }

    /* --- Bereich: leicht daneben --- */
    if (roomDiff >= -0.5 && roomDiff < 0) {
        if (fan === "Q") mqttSend(MQTT.fan, "A");

        if (deltaT05 > 0.1)
            setPoint -= Math.round(deltaT05 * 20 * modeFaktor);
        if (deltaT05 <= 0)
            setPoint -= Math.round(roomDiff * 10 * modeFaktor);

        if (modeFaktor === 1)
            setPoint = Math.max(indoorTemp - limiter + 0.5, setPoint);

        // Grenzen
        if ((modeFaktor === 1 && setPoint > maxTemp) ||
            (modeFaktor === -1 && setPoint < minTemp)) {

            demandPower = Math.min(
                demandPower + 5,
                econoState ? optimalMaxEcono : optimalMax
            );

            setPoint = (modeFaktor === 1) ? indoorTemp : minTemp;
        }
    }

    /* --- Bereich: deutlich daneben --- */
    if (roomDiff < -0.5) {
        if (fan === "Q") mqttSend(MQTT.fan, "A");

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

        if ((modeFaktor === 1 && setPoint > maxTemp) ||
            (modeFaktor === -1 && setPoint < minTemp)) {

            demandPower = Math.min(
                econoState ? optimalMaxEcono : 95,
                demandPower + 5
            );

            setPoint =
                (modeFaktor === 1)
                    ? indoorTemp + (maxTemp - indoorTemp) / 2
                    : minTemp;
        }
    }

    /***************************************************
     * Begrenzen & runden
     ***************************************************/
    setPoint = Math.min(maxTemp, Math.max(minTemp, setPoint));
    setPoint = Math.round(setPoint * 2) / 2; // 0.5 Schritte
    demandPower = Math.max(minPower,
        Math.min(econoState ? optimalMaxEcono : 95, demandPower)
    );

    /***************************************************
     * Frühzeitige Swing-Abschaltung
     ***************************************************/
    if (swingTimer && actualTemp >= setTemp) deactivateSwing();

    /***************************************************
     * MQTT AUSGABE
     ***************************************************/
    mqttSend(MQTT.setPoint, setPoint);
    mqttSend(MQTT.demandPower, demandPower);

    log(`Final → SetPoint: ${setPoint}, Demand: ${demandPower}`, "info");
});

Anmerkungen, Tips, Feedback würde mich freuen.

Wurde eigentlich eine Möglichkeit gefunden, die Lüfterdrezahl des IG bewusst hochzuhalten? Aktuell ist ja selbst auf Stufe 5 alles an Leistung möglich und es läuft nicht optimal beim beheizen anderer Räume.

Heisst das, dass es die Funktion Auto -Bedarfsteuerung mit angeschlossem faikout Modul nicht gibt?

Die gibt es schon noch. Man kann sie nur nicht über das Faikout einstellen.

Die Funktion des Faikout basiert auf dem Reverseengineering des S21-Protokolls. Also wenn jemand ein Kommando findet, worüber die Auto-Bedarfssteuerung gesetzt werden kann, wird revk das sicher ratzfatz einbauen.

Gleiches gilt z.B. für den Bodenauslass bei Truhen.

1 „Gefällt mir“

Naja mit dem Boden Auslass bei den Truhen hatte ich beim Entwickler bereits probiert zu implementierten. Bisher ohne Erfolg.

Da das Wetter momentan im den Nullpunkt ist, taut das Außengerät auch mal ab. Jedes Mal beim Abtauen wird die untere Klappe geschlossen und danach wieder geöffnet. Boa ist die Sch… Klappe laut. Und das im Schlafzimmer.

Habe diese heute aus der Platine abgeklemmt. Heute Nacht der erste Versuch ohne Klappe.

Das Thema mit der Klappe gibt es nur bei der Truhe. Bei den Decken Geräten passiert am Innengerät nichts.

Wie meinst du das? Hast du das S21-Kommando dafür?

Nein, bisher ohne Erfolg. Wegen dem Abtauen bleibt für Klappe einfach immer offen. Funktioniert beim Heizen und Kühlen. So ist der Plan

Nein, das Faikout kennt den Auto Modus nicht. Ob er sich er parallel über Onecta betreiben lässt findet sich häufiger. Es gibt aber Nutzeberichte die das Faikout anstatt eines teureren Daikin WLAN Moduls gekauft haben und berichten, dass die Bedarfssteuerung überhaupt nicht nutzbar ist.

Ist aber alles Angelesen und bei meinen ATXD funktioniert beides soweit ich es getestet hab und der Auto Modus ist schon länger nicht mehr in diesem Spektrum.

Danke für die Info. Nur den letzten Satz habe ich nicht verstanden.