Na dann lass doch mal sehen…
Beiträge von nurmaso
VPN/Proxy erkannt
Es scheint, dass Sie einen VPN- oder Proxy-Dienst verwenden. Bitte beachten Sie, dass die Nutzung eines solchen Dienstes die Funktionalität dieser Webseite einschränken kann.
-
-
Typischerweise hat man Jalousien mit verstellbaren Lamellen. Und mehrere Schaltschwellen der Helligkeit. Also erste Schaltschwelle Jalousie z.B. 80% runter, 2. Schaltschwelle Lamellen schräger, 3. Schaltschwelle Lamellen ganz geschlossen. Somit sitzt man nicht sofort ganz im Dunkeln. An-/Abwesenheit ist hier irrelevant, man will ja das Gebäude vor Aufheizen schützen.
Aber nochmal zu meinem Ansatz. Ich würde einen Shelly Blu Motion und einen BLU H&T mit einem Mini G3 Verknüpfen. Auf der Weboberfläche des Mini dann 2 URL Actions auführen: Wenn Helligkeit vom Blu Motion >Schwellwert, dann setze KVS "luxhigh" auf true; und für temp des H&T eine weitere KVS. Dann in einem Script die beiden Values zyklisch abfragen, und wenn beide true sind, per HTTP.GET den Rolladen-Shelly schließen lassen.
Irgendwie scheint mir das aber doch etwas kompliziert. Kann man das eleganter lösen (ohne cloud)?
-
Ich klinke mich mal ein, weil ich ähnliches vorhabe, aber auch noch nichts konkret habe.
Krauskopp So viele Parameter braucht man doch gar nicht. Temperatur und Helligkeit. So wird es im professionellen Bereich auch nur gemacht. Luxus wäre noch Fassadenausrichtung.
Da ich die Cloud nicht nutze, würde ich einen Blu Motion mit einen 3. Gen verbinden, um die Helligkeit zu holen. Die Temperatur mit einem H&T oder über eine API. Dann könnte man mit einem kleinen Script erstmal nur zufahren.
Und wenn es auch automatisch auffahren soll, müsste man Beruhigungszeiten einbauen, um das von dir genannte Aufschwingen zu verhindern.Und wenn man sich richtig reinnerden will, könnte man mit einiger Mathematik auch die Fassadenausrichtung einfließen lassen…
-
eiche Für meinen Fall reicht es ja, Status On zu erkennen.
Verstehe ich das richtig, dass mit diesem Aufruf die Funktion sync getriggert wird? Also wenn das script mit der ID auf dem Ziel-Shelly läuft, wird die Funktion ausgeführt, sobald die Action ausgelöst wird?
EDIT: getestet, das geht! Geile Sache, danke für den Tipp! 😁
-
Solches kann leicht per Schedule Job alias Zeitplan realisiert werden.
Ich hab die Seiten von deinem link gelesen. Wie das mit sunset oder sogar x vor sunset funktionieren soll, ist mir aber nicht klar. Kannst du das ein bisschen genauer erklären?
-
Nabend und ja, so ist das in dem Script-Beispiel vorgesehen, jedoch läst sich die Funktion "getCurrentState" überall einbauen und aufrufen und ist nicht auf den Aufruf durch diesen einen Eventhandler gebunden.
Die Funktion von getCurrentState ist mir klar. Ich frage bisher mit einer vergleichbaren Funktion zyklisch ab. Ich suche aber nach einer Möglichkeit, dass man nicht zyklisch überwachen muss, sondern der i4 das sofort mitkriegt, wenn eine Lampe eingeschaltet wird. Was ich bisher kenne, ist über einen Eventhandler einen internen Tastendruck zu erfassen; und den Befehl Shelly:StatusOnChange, der aber nur für Geräte in der Cloud funktioniert. Die nutze ich aber nicht.
-
ThomasHRO wenn das Licht nur über den i4 eingeschaltet wird, kann man natürlich mit dem Eventhandler den Tastendruck verarbeiten. Aber ich hab die Lampen auch über Homebridge in Homekit. Wenn man die Lanpen über Siri einschaltet, kriegt man das dann nicht mit.
eiche Hatte ich mich noch gar nicht mit beschäftigt. Aber so wie ich dich verstehe, könnte man damit zB zum Sunset eine Aktion ausführen. Meine Funktion arbeitet aber von 120 Minuten vor Sunset bis zum Sundet zyklisch.
-
Man kann eine Funktion in einen Eventhandler packen? Und damit einen http.get ausführen/abfragen? Das ist crazy 😃
EDIT: hm, wohl falsch verstanden. Der Eventhandler fragt den Tastendruck ab und sendet dann an die Lanpen.
-
Das script läuft munter durch. Ich betrachte den Fehler als gefunden. Dank dir nochmal ostfriese für deine Hilfestellungen. Ich werde das script jetzt weiter optimieren. Die Timer in den Zyklen werd ich rausschmeißen, die waren anscheinend gar nicht nötig; den Zeitabruf evt. nochmal überarbeiten usw.
Noch ein paar Fragen:
Ich würde den Zyklus eigentlich auf 10min hochsetzen, das würde ja völlig reichen. Aber dann würde schlechtestenfalls eine Lampe nach dem Einschalten bis zu 10min nicht abgefragt werden. Kann man den Status eines anderen Gerätes überwachen? Ich habe Shelly:StatusOnChange gefunden, aber das geht nur mit der cloud. Die nutze ich nicht. Gibt es da eine andere Möglichkeit?
Und zu den http.endpoints. Kann man, und wenn ja, wie, die von einem anderen Gerät abrufen? Und sind die bootfest?
-
Ich hab einen (den?) Fehler gefunden!
Im Fehlerfall hatte ich im script immer console.error stehen. Das kennt der shelly wohl nicht. Ich hab das auf console.log korrigiert. Damit wurde die Funktion im Fehlerfall mit Fehlermeldung abgearbeitet und das script lief weiter.
Ich habe trotzdem noch alle 3 Funktionen, die http.get verwenden, mit timeout und try…catch versehen.
Bin gespannt, wie lange das jetzt läuft!
-
Danke für die Erklärung. Das macht Sinn. Werde ich einbauen und Rückmeldung geben
-
Aber das ist hier doch gegeben? Script sollte im Fehlerfall weiterlaufen, oder nicht?
Code
Alles anzeigenfunction 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); } } ); }
-
Mit den http endpoints hab ich verstanden und erfolgreich getestet.
Zu den Beschränkungen gehört aber auch, nur 3 scripts können gleichzeitig laufen. Dann wär der i4 voll. Ich hab in dem Raum noch RGBWs mit denen ich das gleiche machen will und was mit dimmen wollte ich auch noch versuchen.
Also würde ich das gerne in einem script belassen. Was ich weiß: Wenn ich checklampstatus und somit auch setlampcolortemp nicht aufrufe, läuft das script durch. Ich hab den http.get in setlampcolortemp als Verursacher in Verdacht. Kann ich da irgendwie den Fehler fangen, um ihn später auszulesen?
-
Danke für die Anregungen. Die Seiten kannte ich tatsächlich nicht. Zu deinen Punkten noch ein paar Nachfragen:
Welchen Vorteil hätten denn getcomponentstatus.time und .unixtime gegenüber meinem Abruf? Man muss zwar nicht parsen, da das Ergebnis in hh:mm ausgegeben wird. Aber da die Sonnenuntergangszeit in UTC ist, müsste ich beide Zeiten abrufen, und die GMT Differenz errechnen, damit das in Sommer- und Winterzeit funktioniert. Ich glaube, das würde nicht viel kürzer werden.
Wenn du konkrete Stellen oder Ansätze hast, wo ich das script noch vereinfachen kann, gerne her damit.
Auf mehrere scripts aufteilen kann ich machen. Ich dachte die Belastung für den Shelly wäre höher, wenn mehrer scripts parallel laufen. Wieviele dürfen das parallel sein?
Und Variablen übergeben würde ich dann wohl mit kvs.set und get (?)
-
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)
Code
Alles anzeigen// 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(); });
Für allgemeine Vorschläge zur Vereinfachung bin ich ebenso offen 😃
-
Seitdem ich bei den Lampen Off nach Power On eingestellt habe. Scheint der Fehler weg. Das war ja zu einfach...
Wo der Power On aber herkam, bleibt ungeklärt. Ich habe einen Shelly Plus 1 vorgeschaltet. Vielleicht 'flackert' der mal.
-
Waren das nicht 5 Timer pro script?
Ich habe das jetzt, bzw. bin noch dabei in Gruppenarbeit mit Chat GPT 😃 das zu erarbeiten und hab das script fast fertig stabil am laufen.
Callbacks auch für asynchrone funktion wie http.get ist das Stichwort.
Kann ich gerne hier posten, wenn es stabil läuft. Bis dahin melde ich mich, falls ich auf unüberwindbare Probleme stoße.
-
Booten lässt die Lampen unbeeindruckt. An bleibt an, aus bleibt aus.
EDIT: Oder meinst du die Lampen selber? Stand bei allen auf On, hab ich ich jetzt auf Off geändert. Werds weiter beobachten.
-
Ich habe jetzt ein script geschrieben, das alle 10min prüft, ob nur eine von 3 Lampen an ist, und dann ggf. abschaltet. Das behebt zwar nicht den Fehler (den hätte ich schon gern gefunden), aber immerhin die Auswirkung.
-
Hallo,
Wie kann ich das zyklische Ausführen von Funktionen am Besten ausführen?
Ich kenn mich nicht gut aus mit JavaScript, und versuche mir das Stück für Stück zu erarbeiten, aber hier komme ich nicht weiter.
Ich möchte:
1. Funktion A aufrufen. Da ich hier eine url abfrage und parse, dauert es ein paar Sekunden, bis die Funktion abgearbeitet ist; deshalb
2. Funktion B aufrufen mit einem Delay, da sie Variablenwerte braucht, die in Funktion A beschrieben wurden.
3. Funktion A dann alle 24h ausführen,
4. Funktion B alle 60s ausführen.
Es scheitert bei mir schon beim Delay. Das einzige, was ich mit Zeitverzögerung hinbekommen habe, war über Timer.Set.
Hier ist es aber so, dass das Script entweder stehen bleibt ohne Fehler, oder abbricht mit der Meldung To Many Timers, wenn ich 2, 3 und 4 mit Timern aufbaue.
Einzeln laufen beide Funktionen. Noch zu 2., logischer wäre es eigentlich, wenn der Aufruf kommt, wenn A abgearbeitet ist. Aber das hab ich erstrecht nicht hinbekommen.
Wie würde man so etwas aufbauen?
Hier noch Beispielhaft, was ich versucht habe, ist bei mir ganz am Ende des Scripts: