ostfriese und natürlich gern auch alle anderen, hier mein aktuelles script.
Kurze Erläuterung: ich realisiere hiermit HCL, genauer gesagt den abendlichen Wechsel von Kaltweiß zu Warmweiß für 3 Duo Leuchten auf einem i4, beeinflussbar durch ein paar User Parameter.
Dafür müssen natürlich ein paar Sachen abgerufen und berechnet werden. Der Hauptablauf ist einmal die Funktion initscript, die beim Start und dann einmal täglich die nötigen Funktionen aufruft, und die Funktion gradientcycle, die zyklisch (zum Test alle 60s) die entsprechenden Aufrufe macht. Mittels den Verschachtelungen habe ich es erreicht, dass die Funktion einzeln erst abgearbeitet werden, und erst danach (plus Wartezeit) die nächste Funktion aufgerufen wird. Das ist somit meine am stabilsten laufende Version des scripts, aber auch das hält gerne nach ein paar Stunden an.
Hier das script, bei Unklarheiten bitte fragen:
(zum Testen einfach oben in den Variablen 3 echte ip Adressen von Lampen eintragen, sollte so überall laufen)
// User Parameter
let GradDur = 120; // Gradient Duration in Minuten
let GradOffset = 0; // Gradient Offset in Minuten, 0 = Ende Sonnenuntergang
let ColTempMin = 2703; // Min Wert der Farbtemperatur in Kelvin (Shelly Duo = 2703 Warm)
let ColTempMax = 6500; // Max Wert der Farbtemperatur in Kelvin (Shelly Duo = 6500 Kalt)
let initTimerDuration = 5 // Zykluszeit der Initialisierungsschritte in s !!nicht verändern !!
let cycleTimer = 1*60 // Zykluszeit des Gradienten in s
let cycleStepTimer = 5 // Zykluszeit der Einzelschritte des Gradienten in s
let newDayAt = "05:00"; // Uhrzeit der Neuinitalisierung für Abruf Sonnenuntergang und Gradientstatus auf 0
let Lat = "52.5200"; // Breitengrad für Berlin
let Lng = "13.4050"; // Längengrad für Berlin
let ShellyDuoIP1 = "192.168.X.XXX"; // IP-Adresse des ersten Shelly Duo-Geräts
let ShellyDuoIP2 = "192.168.X.XXX"; // IP-Adresse des zweiten Shelly Duo-Geräts
let ShellyDuoIP3 = "192.168.X.XXX"; // IP-Adresse des dritten Shelly Duo-Geräts
// Globale Variablen
let gmtOffsetMinutes = 0; // Differenz GMT zu UTC Zeit in Minuten
let localMinutes = 0; // Uhrzeit in Minuten
let sunsetMinutes = 0; // Sonnenuntergang in Minuten
let gradientEnd = 0; // Gradientende in Minuten
let gradientStart = 0; // Gradientstart in Minuten
let gradientStatus = 0; // 0: vor Start, 1: während, 2: nach Ende des Gradienten
let CurrentColorTemp = ColTempMin; // aktuelle Farbtemperatur
let timeUntilGradientStart = 0; // Minuten bis Gradient startet
let newDayInitCalled = false; // wurde neuer Tag heute schon initialisiert?
function getNumber(string, start, len) {
// Ziffern aus einer Zeichenkette als Zahl, 1. Zeichen hat Index 0
let number = 0;
let factor = Math.pow(10, len - 1);
for (let i = start; i < start + len; i++) {
// Weil bisher keine Typumwandlung funktioniert, Konvertierung über ASCII-Code, "0" => 48
number = number + (string.at(i) - 48 ) * factor;
factor = factor / 10;
}
return number;
}
function calculateMinutesFromMidnight(time) {
// Datum & Zeit extrahieren
let h = getNumber(time, 11, 2);
let m = getNumber(time, 14, 2);
// Umrechnen in Minuten seit Mitternacht
return h * 60 + m;
}
function minutesToTime(minutes) {
// Stunden und Minuten berechnen
let hours = Math.floor(minutes / 60);
let mins = minutes % 60;
// Uhrzeit formatieren
let formattedHours = hours < 10 ? '0' + hours : hours;
let formattedMins = mins < 10 ? '0' + mins : mins;
let formattedTime = formattedHours + ':' + formattedMins;
return formattedTime;
}
function startGradient(callback) {
console.log('Gradient Offset: ' + GradOffset + ' min');
gradientEnd = sunsetMinutes + GradOffset;
gradientStart = gradientEnd - GradDur;
timeUntilGradientStart = gradientStart - localMinutes;
// Überprüfen, ob die aktuelle Zeit vor dem Start des neuen Tages liegt
if (localMinutes < calculateMinutesFromMidnight(newDayAt)) {
// Setze gradientStatus auf 2, um sicherzustellen, dass er nicht auf 0 zurückgesetzt wird
gradientStatus = 2;
} else {
// Überprüfen des Gradientenstatus basierend auf der aktuellen Zeit
if (localMinutes < gradientStart) {
gradientStatus = 0;
} else if (localMinutes >= gradientStart && localMinutes <= gradientEnd) {
gradientStatus = 1;
} else {
gradientStatus = 2;
}
}
console.log('GradientStatus:', gradientStatus);
console.log('Gradient von ' + minutesToTime(gradientStart) + ' Uhr bis ' + minutesToTime(gradientEnd) + ' Uhr');
console.log('Sonnenuntergang um ' + minutesToTime(sunsetMinutes) + ' Uhr');
if (gradientStatus === 0) {
console.log('Gradient beginnt in ', minutesToTime(timeUntilGradientStart) + ' h');
CurrentColorTemp = ColTempMax;
} else if (gradientStatus === 2) {
console.log('Gradient endete vor ' + minutesToTime(localMinutes - gradientEnd) + ' h');
CurrentColorTemp = ColTempMin;
} else if (gradientStatus === 1) {
let remainingMinutes = gradientEnd - localMinutes;
console.log('Gradient läuft noch ' + remainingMinutes + ' Minuten von ' + GradDur + ' Minuten');
CurrentColorTemp = calculateCurrentColorTemp(localMinutes);
// Runden auf ganze Zahl
CurrentColorTemp = Math.round(CurrentColorTemp);
}
console.log('Aktuelle Farbtemperatur: ' + CurrentColorTemp + 'k');
callback();
}
function calculateCurrentColorTemp(currentMinutes) {
// Berechne Fortschritt innerhalb des Gradienten
let progress = (currentMinutes - gradientStart) / GradDur;
// Interpoliere zwischen ColTempMax und ColTempMin basierend auf dem Fortschritt
return ColTempMax - (ColTempMax - ColTempMin) * progress;
}
// Funktion zum Abrufen und Ausgeben der lokalen Zeit und des GMT-Offsets
function getLocalTime(callback) {
let currentDate = new Date();
localMinutes = currentDate.getHours() * 60 + currentDate.getMinutes();
// Extrahieren des GMT-Offsets aus dem toString-Ergebnis
let offsetString = currentDate.toString();
let sign = offsetString.charAt(offsetString.length - 5) === '-' ? '-' : '';
let hoursOffset = offsetString.substr(-5, 3);
let minutesOffset = offsetString.substr(-2);
let gmtOffsetString = sign + hoursOffset + minutesOffset;
gmtOffsetMinutes = parseInt(hoursOffset) * 60 + parseInt(minutesOffset);
console.log("Lokale Zeit:", minutesToTime(localMinutes), "Uhr, GMT", gmtOffsetString);
if (callback) {
callback();
}
}
function setLampColorTemp(ipAddress, colorTemp) {
const url = 'http://' + ipAddress + '/light/0?temp=' + colorTemp;
Shelly.call(
'HTTP.GET',
{
'url': url
},
function (response, error_code, error_msg) {
//console.log('Antwort von HTTP.GET:', response, error_code, error_msg);
if (error_code === 0 && response && response.code && response.code === 200) {
console.log('Farbtemperatur erfolgreich an die Lampe gesendet:', colorTemp);
}
else {
console.error('Fehler: HTTP-Anfrage fehlgeschlagen:', error_msg);
}
}
);
}
function checkLampStatus(ipAddress, callback) {
console.log('Status wird abgerufen von:', ipAddress);
Shelly.call(
'HTTP.GET',
{'url': 'http://' + ipAddress + '/status'},
function (response, error_code, error_msg) {
//console.log('Antwort von HTTP.GET:', response, error_code, error_msg);
if (error_code === 0 && response && response.code && response.code === 200) {
const responseData = JSON.parse(response.body);
const lampStatus = responseData.lights[0].ison;
const colorTemp = responseData.lights[0].temp;
if (lampStatus) {
console.log('Die Lampe ' + ipAddress + ' ist eingeschaltet. Farbtemperatur: ' + colorTemp);
if (colorTemp !== CurrentColorTemp) {
console.log('Die Farbtemperatur unterscheidet sich von der gespeicherten Farbtemperatur.');
setLampColorTemp(ipAddress, CurrentColorTemp); // Aktualisierte Farbtemperatur übergeben
//console.log('Die Farbtemperatur wurde aktualisiert:', CurrentColorTemp);
} else {
console.log('Die Farbtemperatur stimmt mit der gespeicherten Farbtemperatur überein.');
}
} else {
console.log('Die Lampe ' + ipAddress + ' ist ausgeschaltet.');
}
// Aufruf der Callback-Funktion, wenn die Funktion abgeschlossen ist
if (callback) {
callback();
}
}
else {
console.error('Fehler: HTTP-Anfrage fehlgeschlagen:', error_msg);
// Aufruf der Callback-Funktion auch im Fehlerfall, falls gewünscht
if (callback) {
callback();
}
}
}
);
}
function callSunset(callback) {
Shelly.call('HTTP.GET', { url: 'https://api.sunrise-sunset.org/json?lat=' + Lat + '&lng=' + Lng + '&formatted=0' },
function(res, error_code, error_msg, ud) {
if (error_code === 0 && res.code === 200) { // Überprüfen, ob der API-Aufruf erfolgreich war
let obj = JSON.parse(res.body);
let sunset = obj.results.sunset;
let sunsetUTCMinutes = calculateMinutesFromMidnight(sunset); // Sonnenuntergang in UTC-Minuten
sunsetMinutes = sunsetUTCMinutes + gmtOffsetMinutes; // Sonnenuntergang in Ortszeit-Minuten
// Umrechnung in Stunden und Minuten
let sunsetTime = minutesToTime(sunsetMinutes);
console.log('Sonnenuntergang abgerufen: ' + sunsetTime + ' Uhr (Ortszeit)'); // Ausgabe in Stunden und Minuten
// Rückruf aufrufen, nachdem der Sonnenuntergang abgerufen wurde
callback();
} else {
console.error('Fehler beim Abrufen des Sonnenuntergangs:', error_msg);
}
}
);
}
function initScript() {
console.log("*****Initialisiere Script*****");
newDayInitCalled = true; // neuer Tag wurde initialisiert
let functions = [
getLocalTime,
callSunset,
startGradient,
function() {
console.log("*****Initialisierung abgeschlossen*****");
}
];
let index = 0;
function executeNextFunction() {
if (index < functions.length) {
functions[index++](function() {
Timer.set(initTimerDuration * 1000, false, executeNextFunction); // Verwendung von initTimerDuration
});
}
}
executeNextFunction();
}
function GradientCycle() {
let functions = [
getLocalTime,
startGradient,
function(callback) {
// Array mit den IP-Adressen der Lampen
let lampIPs = [ShellyDuoIP1, ShellyDuoIP2, ShellyDuoIP3];
// Funktion, um rekursiv die Lampen zu überprüfen
function checkNextLamp(index) {
if (index < lampIPs.length) {
// Überprüfe die Lampe und rufe die nächste Lampe auf, wenn abgeschlossen
checkLampStatus(lampIPs[index], function() {
// Timer für die Pausen zwischen den Überprüfungen der Lampen
Timer.set(cycleStepTimer * 1000, false, function() {
checkNextLamp(index + 1);
});
});
} else {
// Alle Lampen überprüft, führe die nächste Aktion aus
console.log("Alle Lampen überprüft.");
callback();
}
}
// Starte die Überprüfung der Lampen
checkNextLamp(0);
}
];
let index = 0;
function executeNextFunction() {
if (index < functions.length) {
functions[index++](function() {
Timer.set(cycleStepTimer * 1000, false, executeNextFunction); // Verwendung von cycleStepTimer
});
} else {
// Nur wenn alle Funktionen abgeschlossen sind, rufe checkNewDayInit auf
checkNewDayInit();
}
}
// Starte den Ausführungszyklus
executeNextFunction();
}
function checkNewDayInit(callback) {
// Extrahiere Stunden und Minuten aus newDayAt
let newDayHours = parseInt(newDayAt.split(':')[0]);
let newDayMinutes = parseInt(newDayAt.split(':')[1]);
// Konvertiere newDayAt in Minuten seit Mitternacht
let newDayAtMinutes = newDayHours * 60 + newDayMinutes;
// Überprüfe, ob es ein neuer Tag ist (0:00 Uhr erreicht wurde)
if (localMinutes < newDayAtMinutes) {
newDayInitCalled = false; // Setze das Flag auf false, um sicherzustellen, dass initScript() am neuen Tag aufgerufen wird
}
// Überprüfe, ob die aktuelle Zeit das neue Tag-Startdatum erreicht oder überschreitet
if (localMinutes >= newDayAtMinutes && !newDayInitCalled) {
initScript(); // rufe initScript auf, wenn das neue Datum erreicht ist
}
// Führe den Rückruf aus
if (callback) {
callback();
}
}
// Initialer Aufruf der Funktionen
initScript();
// Aufruf der GradientCycle-Funktion
Timer.set(cycleTimer * 1000, true, function() {
GradientCycle();
});
Alles anzeigen
Für allgemeine Vorschläge zur Vereinfachung bin ich ebenso offen 😃