// 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.');