logo elektroda
logo elektroda
X
logo elektroda

Script to Control Electric Blinds Using PV Solar Output as Sunlight Sensor Input

io2345 321 4
ADVERTISEMENT
  • #1 21538532
    io2345
    Level 8  
    I need to substitute a light sensor, that was used to shut electric blinds on sunny days to prevent overheating of rooms.
    I searched the WWW for Tuya light sensors, but nothing sounded too promising - either the comments were bad, or they could only measure low light but no sunlight.

    Now I have a different idea for an approach: I have some solar panels running on a PV system. They deal with light anyway. All I need is a clever script, that detects proper conditions for open and close conditions: For example, if the solar power production is over 50% of the maximum value, the blinds close. If it drops 25% and stays that way for a couple of minutes (like 10), the blinds will go up. If it drops significantly in shorter time (like 50% in one minute) the blinds will go up even faster, as this might significate that a thunderstorm is coming up.

    Does anyone already have a script for that purpose up and running that is proofed to work reliable?
  • ADVERTISEMENT
  • #2 21539900
    xury
    Automation specialist
    Rather, no one has a ready-made script. It is not even clear where the data for it would be taken from.
    You could use, for example, the BH1750 sensor and Tasmot.
    And the script if you describe it well will generate Chatgpt or Deepseek for you.
  • ADVERTISEMENT
  • #3 21540581
    io2345
    Level 8  
    >>21539900 Thank you for responding. Of course, I could introduce another smart element - but why, if there is already a reliable, waterproof, long-life "sensor" that already delivers data to the smart home system?

    Hinzugefügt nach 1 [Stunden] 57 [Minuten]:

    Let me share my thoughts with you: The original sensor that I did use, was wirelessly connected to three proprietary "intelligent" blind controls. The first one died after three years, the next one did last four years. Each one around 50€.
    What did the sensor do? It had a very small solar panel on the front side (facing to the window), and measured the voltage of the panel. If it was over an adjustable level for 10 minutes, the blinds would go down. That's all it did. 100€ for two sensors and 100€ for each blind control, so 400€ for the whole system. A lot of money.
    So I decided to substitute the whole stuff with cheaper stuff. Tuya blind controls are about 5€ each. I already ordered three of them for 4,29€ each. OK, I also need manual switches that fit in the electrical appliances series. They are 15€ each. In total not even 20€ per control.
    Next I thought about a substitute for the sensor. My first idea was to copy, what the original did: Measuring the voltage of a small solar panel and transmitting that via ESP or something like that to the Smart Home system (ioBroker in my case). But I would have needed at least a small case and a power source. The disadvantage of a sensor attached to a window is, that you can never close the blinds on this window fully, as then the sensor would not work anymore as intended or open the blinds again by itself.
    And then I finally had the idea, that there are already solar panels on the roof, which are transmitting their power value to the smart home system every 20 seconds, so why not using them.
    This solution is even better, as you can take in consideration the status of the room's thermo switch (the one used for floor heating. I get their status from this project: Link). If the room is cold and therefore the thermo switch is on, leave the blinds up on a sunny day and let the sun warm up the room. :-)
  • ADVERTISEMENT
  • #4 21546117
    io2345
    Level 8  
    Here is a script, that I'm using currently (in ioBroker). It closes and opens blinds depending on the solar production, outside temperature and state of the room thermostat. Script is inactive below a given outside temperature. If the solar production (as an indicator of the power of sunlight) is high enough, it will close the blinds, unless the room heating (status of thermostat) is off. If the room thermostat is on and thus the heating on, the blinds will go up in order to let the sun help to warm up the room. Additionally the up/down commands are blocked for a couple of minutes after open/close of blinds, to avoid constant up/down cycles on cloudy days.


    // === Setze den tatsächlichen Datenpunkt ===
    const PV_INPUT_DP = 'javascript.0.Geräte.Photovoltaik.Leistung_aktuell';  // Der tatsächliche Datenpunkt für die PV-Leistung
    const PV_AVG_DP = '0_userdata.0.EigeneDatenpunkte.Wertesammler.PV-Mittelwert_Hausdach'; // Mittelwert-Datenpunkt
    const TEMP_SENSOR_DP = 'mqtt.0.Vito.WP.Außentemperatur'; // Außentemperatur-Datenpunkt
    
    // === Jalousien und deren Datenpunkte ===
    const jalousien = [
        {
            name: 'KA', // Name der Jalousie
            dpUp: 'mqtt.0.Jalousie_KA.6.set', // Datenpunkt für das Hochfahren der Jalousie
            dpDown: 'mqtt.0.Jalousie_KA.1.set', // Datenpunkt für das Runterfahren der Jalousie
            thermo: 'mqtt.0.6-channel-relay.13.get' // Datenpunkt des zugehörigen Raumthermostats
        },
        {
            name: 'KI',
            dpUp: 'mqtt.0.Jalousie_KI.6.set',
            dpDown: 'mqtt.0.Jalousie_KI.1.set',
            thermo: 'mqtt.0.6-channel-relay.15.get'
        },
        {
            name: 'WO',
            dpUp: 'mqtt.0.Jalousie_WO.6.set',
            dpDown: 'mqtt.0.Jalousie_WO.1.set',
            thermo: 'mqtt.0.6-channel-relay.14.get'
        }
    ];
    
    // === Zeit- und Sperrzeiten ===
    const LOCK_TIME = 8 * 60 * 1000; // Sperrzeit für Jalousien, wenn sie vom Script geschlossen wurden (8 Minuten)
    const JALOUSIE_DURATION = 28 * 1000; // Dauer des Schließens/Öffnens (30 Sekunden)
    const JALOUSIE_OPEN_DURATION = 30 * 1000; // Öffnungsdauer der Jalousie
    const OPEN_LOCK_TIME = 2 * 60 * 1000; // Sperrzeit nach Öffnen der Jalousie
    const TEMP_LIMIT = 9; // Grenzwert der Außentemperatur, unterhalb dessen wird die Jalousie wieder geöffnet
    const PV_LOW_THRESHOLD = 825; // Schwellenwert für den Mittelwert der PV-Leistung, unterhalb dessen die Jalousie schneller öffnen muss
    const PV_HIGH_THRESHOLD = 2800; // Schwellenwert für den Mittelwert der PV-Leistung, über dem die Jalousie geschlossen werden soll
    
    // === Datenpunkte für die Speicherung ===
    let jalousieLock = {}; // Speichert die Sperrzeit für jede Jalousie
    let lastCloseTime = {}; // Speichert den Zeitpunkt des letzten Schließens jeder Jalousie
    let lastChangeTime = {}; // Speichert die Zeit der letzten Änderung des Mittelwerts
    let openLock = {}; // Sperrzeit nach Öffnen der Jalousie
    let jalousieStatus = {}; // Speichert den aktuellen Status der Jalousie (offen/geschlossen)
    
    let lastAvgPV = null;  // Speichert den letzten Mittelwert der PV-Anlage
    let lastAvgPVTime = null;  // Speichert die Zeit der letzten Mittelwert-Berechnung
    
    // === Funktion zur Berechnung des Mittelwerts ===
    let pvValues = [];  // Array zum Speichern der letzten 7 Messwerte
    
    function calculateAvgPV() {
        const pvValue = getState(PV_INPUT_DP).val; // Abrufen der aktuellen PV-Leistung
        
        // Überprüfen, ob der PV-Wert gültig ist (nicht null oder NaN)
        if (pvValue !== null && !isNaN(pvValue)) {
            pvValues.push(pvValue); // Aktuellen Wert in das Array einfügen
        }
    
        // Nur die letzten 7 Werte berücksichtigen
        if (pvValues.length > 7) {
            pvValues.shift(); // Ältesten Wert entfernen, wenn mehr als 7 Werte vorhanden sind
        }
    
        // Berechnung des Mittelwerts
        const sum = pvValues.reduce((acc, val) => acc + val, 0); // Summe aller Werte
        const avgPV = pvValues.length > 0 ? sum / pvValues.length : 0; // Mittelwert berechnen
    
        // Den berechneten Mittelwert auf die nächsten 2 Nachkommastellen runden
        return Math.round(avgPV);
    }
    
    // === Funktion zur Überprüfung der Jalousien-Logik ===
    function checkJalousien(avgPV) {
        // Außentemperatur prüfen
        const currentTemp = getState(TEMP_SENSOR_DP).val;
        
        // Wenn Außentemperatur unter TEMP_LIMIT, keine Jalousien schließen
        if (currentTemp < TEMP_LIMIT) {
            //console.log(`Außentemperatur ist unter ${TEMP_LIMIT}°C. Jalousien werden nicht geschlossen.`);
            return; // Verhindert, dass die Jalousien geschlossen werden
        }
    
        //console.log(`Aktueller Mittelwert der PV-Leistung: ${avgPV} Watt`);
    
        jalousien.forEach(j => {
            const thermo = getState(j.thermo).val;
            const nowTime = new Date().getTime(); // Aktuelle Zeit
    
            // Zustandsüberprüfung für die Sperrzeiten und letzten Änderungen
            const locked = nowTime - jalousieLock[j.name] < LOCK_TIME;
            const isClosed = jalousieStatus[j.name] === 'geschlossen';
            const isOpen = jalousieStatus[j.name] === 'offen';
            const openLocked = nowTime - openLock[j.name] < OPEN_LOCK_TIME; // Sperrzeit nach Öffnen
            const lastCloseTimeDiff = nowTime - (lastCloseTime[j.name] || 0); // Zeit seit dem letzten Schließen
    
            // **Neue Logik für das schnelle Öffnen bei schnellen Änderungen des Mittelwerts**
            // Wenn der Mittelwert innerhalb von 2 Minuten von >2800 auf <825 gesenkt wurde, sofort öffnen
            if (lastAvgPV !== null && lastAvgPV > PV_HIGH_THRESHOLD && avgPV < PV_LOW_THRESHOLD && lastAvgPVTime && (nowTime - lastAvgPVTime <= 2 * 60 * 1000)) {
                setTimeout(() => {
                    setState(j.dpUp, 1);  // Hier ändern wir den Wert von true auf 1
                    setTimeout(() => setState(j.dpUp, 0), JALOUSIE_OPEN_DURATION);  // Nach der Öffnungszeit den Wert auf 0 setzen
                }, 0);
                jalousieStatus[j.name] = 'offen'; // Jalousie-Status auf "offen" setzen
                openLock[j.name] = nowTime; // Sperrzeit setzen
                log(`[${j.name}] Mittelwert fiel innerhalb von 2 Minuten von > 2800 auf < 825, Jalousie wird sofort geöffnet.`);
            }
    
            // Jalousie schließen, wenn Mittelwert > 2800 und Thermostatstatus = 0
            if (avgPV >= PV_HIGH_THRESHOLD && thermo === 0 && !isClosed && !locked && !openLocked) {
                setTimeout(() => {
                    setState(j.dpDown, 1);  // Wert für das Schließen auf 1 setzen
                    setTimeout(() => setState(j.dpDown, 0), JALOUSIE_DURATION);  // Nach der Schließzeit den Wert auf 0 setzen
                }, 0);
                jalousieStatus[j.name] = 'geschlossen'; // Jalousie-Status auf "geschlossen" setzen
                lastCloseTime[j.name] = nowTime; // Speichern des Schließzeitpunkts
                jalousieLock[j.name] = nowTime;
                console.log(`[${j.name}] Jalousie wird geschlossen.`);
            }
    
            // Jalousie öffnen, wenn Mittelwert < 2800 und mehr als 8 Minuten seit dem Schließen vergangen sind
            if (avgPV < PV_HIGH_THRESHOLD && lastCloseTimeDiff >= LOCK_TIME && isClosed && !openLocked) {
                setTimeout(() => {
                    setState(j.dpUp, 1);  // Wert für das Öffnen auf 1 setzen
                    setTimeout(() => setState(j.dpUp, 0), JALOUSIE_OPEN_DURATION);  // Nach der Öffnungszeit den Wert auf 0 setzen
                }, 0);
                jalousieStatus[j.name] = 'offen'; // Jalousie-Status auf "offen" setzen
                openLock[j.name] = nowTime; // Sperrzeit setzen
                console.log(`[${j.name}] Jalousie wird geöffnet (Mittelwert < 2800 und mehr als 8 Minuten seit dem Schließen).`);
            }
    
            // Wenn Thermostat auf 1 geht, Jalousie sofort öffnen, wenn Mittelwert > 3025
            if (thermo === 1 && !isOpen && avgPV > PV_HIGH_THRESHOLD && !openLocked) {
                setTimeout(() => {
                    setState(j.dpUp, 1);  // Wert für das Öffnen auf 1 setzen
                    setTimeout(() => setState(j.dpUp, 0), JALOUSIE_OPEN_DURATION);  // Nach der Öffnungszeit den Wert auf 0 setzen
                }, 0);
                jalousieStatus[j.name] = 'offen'; // Jalousie-Status auf "offen" setzen
                openLock[j.name] = nowTime; // Sperrzeit setzen
                console.log(`[${j.name}] Thermostat auf 1, Jalousie wird sofort geöffnet (Mittelwert > 2800).`);
            }
        });
    
        // Den letzten Mittelwert und die Zeit speichern
        lastAvgPV = avgPV;
        lastAvgPVTime = new Date().getTime();
    }
    
    // === Funktionsaufruf ===
    on({id: PV_INPUT_DP, change: 'ne'}, async function (obj) {
        const avgPV = calculateAvgPV();
        setState(PV_AVG_DP, avgPV); // Den berechneten Mittelwert setzen
        lastChangeTime = new Date().getTime(); // Letzte Änderung des Mittelwerts speichern
        checkJalousien(avgPV); // Jalousien nach der Mittelwertberechnung überprüfen
    });
  • #5 21560851
    io2345
    Level 8  
    Latest version, which also detects manual button press of wall switch (I use cheap standard mechanical switches connected to the small controller from here: Link) and deactivates the script control of a specific blind individually for a certain time. Also includes a data point for Override Switch (can be used e.g. for window cleaning). It excludes sleeping rooms from opening/closing blinds in the morning before a certain time in order not to wake you up. And it has an emergency opening mechanism to open blinds, if PV-value drops very fast in short time (which could mean a stormfront is pulling up). Also some smaller improvements, like a better protection from blinds constantly moving up and down too much.

    // Dieses Script steuert die Jalousien in Abhängigkeit von Sonneneinstrahlung (ermittelt aus
    // der PV-Leistung), Außentemperatur (Script bei niedriger Temperatur inaktiv) sowie dem Status
    // von Raumthermostaten (bei Sonne und laufender Heizung Jalousien auf). Es enthält mehrere Locks,
    // die ein zu häufiges, aufeinanderfolgendes Öffnen/Schließen verhindern (jalousieLock und openLock). 
    // Es enthält einen Datenpunkt, über den es zentral deaktiviert werden kann (Override). Nach
    // manueller Betätigung ist das Script für diese Jalousie blockiert. Die Blockade wird automatisch
    // täglich um 11 Uhr und 21 Uhr deaktiviert. Die Funktion "Schnellöffnung bei Gewitter" funktioniert
    // aber immer.
    
    // === Grundkonfiguration: Datenpunkte für Sensoren und Steuerung ===
    const PV_INPUT_DP = 'javascript.0.Geräte.Photovoltaik.Leistung_aktuell';  // Der Datenpunkt (DP) der den PV-Wert liefert
    const PV_AVG_DP = '0_userdata.0.EigeneDatenpunkte.Wertesammler.PV-Mittelwert_Hausdach'; // DP muss angelegt werden
    const PV_AVG_LONG_DP = '0_userdata.0.EigeneDatenpunkte.Wertesammler.PV-Mittelwert_Hausdach_lang'; // DP muss angelegt werden
    const TEMP_SENSOR_DP = 'mqtt.0.Vito.WP.Außentemperatur'; // Der DP der die Außentemperatur liefert
    const CENTRAL_OVERRIDE = '0_userdata.0.Jalousiescript_aktiviert'; // DP muss angelegt werden. Ist er FALSE, ist die Scriptsteuerung weitgehend deaktiviert
    
    // === Konfiguration der Jalousien (Up/Down/Status) ===
    const jalousien = [
       { name: 'WO', dpUp: 'mqtt.0.Jalousie_WO.6.set', dpDown: 'mqtt.0.Jalousie_WO.1.set', thermo: 'mqtt.0.HK_Relais_DG.14.get' }, //DPs der Jalousiesteuerung und der Thermostate
       { name: 'KI', dpUp: 'mqtt.0.Jalousie_KI.6.set', dpDown: 'mqtt.0.Jalousie_KI.1.set', thermo: 'mqtt.0.HK_Relais_DG.15.get' },
       { name: 'KA', dpUp: 'mqtt.0.Jalousie_KA.6.set', dpDown: 'mqtt.0.Jalousie_KA.1.set', thermo: 'mqtt.0.HK_Relais_DG.13.get' }
    ];
    
    // === Zeit- und Schwellenwerte ===
    const LOCK_TIME = 12 * 60 * 1000;  // So lange bleibt die Jalousie nach Schließen mindestens zu, z.B. 12 Minuten
    const OPEN_LOCK_TIME = 5 * 60 * 1000; // So lange bleibt die Jalousie nach Öffnen mindestens auf, z.B. 5 Minuten
    const JALOUSIE_DURATION = 31 * 1000;  //Laufzeit der Jalousie zum Schließen, z.B. 31 Sekunden
    const JALOUSIE_OPEN_DURATION = 34 * 1000; //Laufzeit der Jalousie zum Öffnen, z.B. 34 Sekunden
    const TEMP_LIMIT = 9; //Unterhalb dieser Temperatur in °C ist das Script weitgehend inaktiv
    const PV_LOW_THRESHOLD = 700; // Wert in Watt, auf die der PV-Wert in kurzer Zeit fallen muss für Schnellöffnung
    const PV_HIGH_THRESHOLD = 2400; // Wert in Watt, bei der die Jalousien schließen
    const PV_OPEN_THRESHOLD = PV_HIGH_THRESHOLD * 0.9; // Prozentwert, bei dem die Jalousien öffnen
    const PV_LONG_TERM_VALUES = 30; // Anzahl der gelieferten PV-Werte, mit der der PV-Durchschnittswert Langzeit gebildet wird
    const SLEEPROOM_ACTIVE_HOUR = 11; // Uhrzeit, bis zu der die Schlafräume von Scriptaktionen ausgenommen sind
    const SCRIPT_TRIGGER_WINDOW = 15000;  
    
    // === Variablen zur Statusverfolgung ===
    let jalousieLock = {};
    let lastCloseTime = {};
    let openLock = {};
    let jalousieStatus = {};
    let scriptTriggered = {};
    let manualLockLogged = {};
    let pvValues = [];
    let pvLongTermValues = [];
    let lastAvgPV = null;
    let lastAvgPVTime = null;
    let fastOpenByPV = {};
    let fastOpenTime = {};
    
    // === Mittelwertberechnung ===
    function calculateMovingAverage(arr, maxLength) {
        if (arr.length > maxLength) arr.shift();
        const sum = arr.reduce((a, b) => a + b, 0);
        return Math.round(arr.length ? sum / arr.length : 0);
    }
    
    jalousien.forEach(j => {
        if (!jalousieStatus[j.name]) {
            jalousieStatus[j.name] = 'geschlossen';
        }
    });
    
    // === Statusdatenpunkt schreiben ===
    function updateRolloStatus(name, percent) {
        setState(`0_userdata.0.Rollo_${name}`, percent, true);
    }
    
    function markScriptTrigger(name) {
        scriptTriggered[name] = true;
        setTimeout(() => { scriptTriggered[name] = false; }, SCRIPT_TRIGGER_WINDOW);
    }
    
    // === Hauptlogik zur Steuerung der Jalousien ===
    function checkJalousien(avgPVLong, avgPVShort) {
        const now = Date.now();
        const hour = new Date().getHours();
    
        const fastOpenPossible = (
            lastAvgPV !== null &&
            lastAvgPV > PV_HIGH_THRESHOLD &&
            avgPVShort < PV_LOW_THRESHOLD &&
            lastAvgPVTime && now - lastAvgPVTime <= 3 * 60 * 1000
        );
    
       // === Schnellöffnung bei PV-Abfall ===
        if (fastOpenPossible) {
            log('Schnellöffnung aufgrund PV-Abfall innerhalb 3 Minuten aktiviert.');
            jalousien.forEach(j => {
                if (jalousieStatus[j.name] !== 'offen') {
                    openLock[j.name] = 0;
                    jalousieLock[j.name] = 0;
    
                    log(`[${j.name}] Schnellöffnung: Jalousie wird geöffnet (Lock zurückgesetzt).`);
    
                    updateRolloStatus(j.name, 0);
                    setState(j.dpUp, 1);
                    setTimeout(() => setState(j.dpUp, 0), JALOUSIE_OPEN_DURATION);
    
                    jalousieStatus[j.name] = 'offen';
                    openLock[j.name] = now;
                    fastOpenByPV[j.name] = true;
                    fastOpenTime[j.name] = now;
    
                    markScriptTrigger(j.name);
                } else {
                    log(`[${j.name}] Schnellöffnung: Jalousie ist bereits offen.`);
                }
            });
            return;
        }
    
        const currentTemp = getState(TEMP_SENSOR_DP).val;
        if (currentTemp < TEMP_LIMIT) {
            log(`Temperatur unter TEMP_LIMIT (${TEMP_LIMIT} °C). Hauptlogik wird nicht ausgeführt.`);
            return;
        }
    
        if (!getState(CENTRAL_OVERRIDE).val) {
            log('Zentral-Override aktiv – Automatik deaktiviert.');
            return;
        }
    
     // === PV-basierte Logik ===
        jalousien.forEach(j => {
            const disableDP = `0_userdata.0.JalousieDisable.${j.name}`;
            const disabled = getState(disableDP)?.val === true;
            const isClosed = jalousieStatus[j.name] === 'geschlossen';
            const isOpen = jalousieStatus[j.name] === 'offen';
            const locked = now - (jalousieLock[j.name] || 0) < LOCK_TIME;
            const openLocked = now - (openLock[j.name] || 0) < OPEN_LOCK_TIME;
            const thermo = getState(j.thermo).val;
    
            if (disabled) {
        if (!manualLockLogged[j.name]) {
            log(`[${j.name}] Automatik deaktiviert durch manuelle Bedienung.`);
            manualLockLogged[j.name] = true;
        }
        return;
        } else {
        if (manualLockLogged[j.name]) {
            log(`[${j.name}] Automatik wieder aktiviert.`);
            manualLockLogged[j.name] = false;
        }
        }
    
            if ((j.name === 'KA' || j.name === 'KI') && hour < SLEEPROOM_ACTIVE_HOUR) {
                log(`[${j.name}] Schlafraum – Automatik gesperrt bis ${SLEEPROOM_ACTIVE_HOUR} Uhr.`);
                return;
            }
    
            if (currentTemp < TEMP_LIMIT && isClosed && !openLocked) {
                updateRolloStatus(j.name, 0);
                markScriptTrigger(j.name);
                setState(j.dpUp, 1);
                setTimeout(() => setState(j.dpUp, 0), JALOUSIE_OPEN_DURATION);
                jalousieStatus[j.name] = 'offen';
                openLock[j.name] = now;
                log(`[${j.name}] Temperatur < ${TEMP_LIMIT} °C – Jalousie geöffnet.`);
                return;
            }
            // === Schließen bei ausreichend PV ===
    
            if (avgPVLong >= PV_HIGH_THRESHOLD && thermo === 0 && !isClosed && !locked && !openLocked) {
                log(`[${j.name}] PV hoch (${avgPVLong}) und Thermostat inaktiv, Jalousie wird geschlossen.`);
                updateRolloStatus(j.name, 100);
                markScriptTrigger(j.name);
                setState(j.dpDown, 1);
                setTimeout(() => setState(j.dpDown, 0), JALOUSIE_DURATION);
                jalousieStatus[j.name] = 'geschlossen';
                jalousieLock[j.name] = now;
                lastCloseTime[j.name] = now;
                return;
            } else {
                if (locked) log(`[${j.name}] Lock aktiv – kein Schließen.`);
                if (openLocked) log(`[${j.name}] OpenLock aktiv – kein Schließen.`);
            }
    
            // === Öffnen bei PV-Rückgang (nur wenn PV_OPEN_THRESHOLD unterschritten) ===
            if (avgPVLong < PV_OPEN_THRESHOLD && isClosed && !openLocked &&
                (!lastCloseTime[j.name] || now - lastCloseTime[j.name] >= LOCK_TIME)) {
                log(`[${j.name}] PV Rückgang erkannt (${avgPVLong} < ${PV_OPEN_THRESHOLD}), Jalousie wird geöffnet.`);
                updateRolloStatus(j.name, 0);
                markScriptTrigger(j.name);
                setState(j.dpUp, 1);
                setTimeout(() => setState(j.dpUp, 0), JALOUSIE_OPEN_DURATION);
                jalousieStatus[j.name] = 'offen';
                openLock[j.name] = now;
                return;
            }
            // === Öffnung bei aktivem Thermostat ===
            if (thermo === 1 && !isOpen && avgPVLong > PV_HIGH_THRESHOLD && !openLocked) {
                log(`[${j.name}] Thermostat aktiv & PV hoch (${avgPVLong}), Jalousie wird geöffnet.`);
                updateRolloStatus(j.name, 0);
                markScriptTrigger(j.name);
                setState(j.dpUp, 1);
                setTimeout(() => setState(j.dpUp, 0), JALOUSIE_OPEN_DURATION);
                jalousieStatus[j.name] = 'offen';
                openLock[j.name] = now;
                return;
            }
        });
    
       // log('--- checkJalousien beendet ---');
    }
    
    // === Reaktion auf neue PV-Daten (Trigger) ===
    on({ id: PV_INPUT_DP, change: 'ne' }, () => {
        const val = getState(PV_INPUT_DP).val;
        if (val != null && !isNaN(val)) {
            pvValues.push(val);
            pvLongTermValues.push(val);
        }
    
        const avgShort = calculateMovingAverage(pvValues, 7);
        const avgLong = calculateMovingAverage(pvLongTermValues, PV_LONG_TERM_VALUES);
        setState(PV_AVG_DP, avgShort);
        setState(PV_AVG_LONG_DP, avgLong);
    
        if (avgShort > PV_HIGH_THRESHOLD) {
            lastAvgPV = avgShort;
            lastAvgPVTime = Date.now();
        }
    
        checkJalousien(avgLong, avgShort);
    });
    
    // === Überwachung manueller Steuerung über Taster ===
    jalousien.forEach(j => {
        const dpUpGet = j.dpUp.replace('.set', '.get');
        const dpDownGet = j.dpDown.replace('.set', '.get');
        let upStart = null;
        let downStart = null;
    
        function handleManualTrigger(name) {
            if (!scriptTriggered[name]) {
                const disableDP = `0_userdata.0.JalousieDisable.${name}`;
                setState(disableDP, true);
                log(`[${name}] Manuelle Bedienung erkannt – Automatik deaktiviert bis 11 Uhr oder 21 Uhr.`);
            }
        }
    
        on({ id: dpUpGet, change: 'ne' }, obj => {
        if (obj.state.val === 1) {
            if (!scriptTriggered[j.name]) {
                handleManualTrigger(j.name);
            } else {
                log(`[${j.name}] Trigger durch Script erkannt – kein manueller Eingriff (UP).`);
            }
            upStart = Date.now();
        } else if (obj.state.val === 0 && upStart) {
            const duration = Date.now() - upStart;
            const percent = Math.max(0, Math.min(100, 100 - Math.round((duration / JALOUSIE_OPEN_DURATION) * 100)));
            updateRolloStatus(j.name, percent);
            upStart = null;
            log(`[${j.name}] Manuelle Öffnung Dauer: ${duration}ms, Status: ${percent}%`);
        }
    });
    
    on({ id: dpDownGet, change: 'ne' }, obj => {
        if (obj.state.val === 1) {
            if (!scriptTriggered[j.name]) {
                handleManualTrigger(j.name);
            } else {
                log(`[${j.name}] Trigger durch Script erkannt – kein manueller Eingriff (DOWN).`);
            }
            downStart = Date.now();
        } else if (obj.state.val === 0 && downStart) {
            const duration = Date.now() - downStart;
            const percent = Math.max(0, Math.min(100, Math.round((duration / JALOUSIE_DURATION) * 100)));
            updateRolloStatus(j.name, percent);
            downStart = null;
            log(`[${j.name}] Manuelle Schließung Dauer: ${duration}ms, Status: ${percent}%`);
        }
    });
    
    });
    
    // === Locks nach manueller Bedienung zurücksetzen ===
    schedule('0 11 * * *', () => {
        jalousien.forEach(j => {
            setState(`0_userdata.0.JalousieDisable.${j.name}`, false);
            log(`[${j.name}] Automatik-Disable zurückgesetzt um 11 Uhr.`);
        });
    });
    
    schedule('0 21 * * *', () => {
        jalousien.forEach(j => {
            setState(`0_userdata.0.JalousieDisable.${j.name}`, false);
            log(`[${j.name}] Automatik-Disable zurückgesetzt um 21 Uhr.`);
        });
    });
    
    log('Jalousiescript gestartet. Automatik aktiv.');
ADVERTISEMENT