NEWS
[Javascript+Blockly] Markise automatisiert nach Sonnenstand
-
Nachdem ich einen Motor in unsere Markise eingebaut habe, wollte ich sie dann auch automatisiert nach Sonnenstand fahren lassen. So, dass sie immer nur so weit ausfährt, dass unser Balkon komplett beschattet ist.
So ist die (Einbau-) Situation bei uns (Sonne ist gerade herum gekommen):
Letztes Jahr hatte ich mir ein Blockly Script gebastelt, dass mit den aktuellen Sonnenwinkeln (Azimut + Elevation) annäherungsweise automatisiert fährt. Das funktioniert so leidlich, vor allem je weiter das Jahr voran schreitet. Auch ist die Situation - die Sonne steht fast parallel zum Balkon - sehr schwer abzubilden. Hier spielt der Azimut eine größere Rolle als die Elevation der Sonne.
Also wollte ich rechnen. Mathematisch ist klar, es braucht die Komponenten Azimut und Elevation der Sonne, Montagehöhe der Markise, horizontaler Winkel der Markise (in welchem Winkel sie beim ausfahren nach unten abfällt), Ausrichtung / Himmelsrichtung des Balkons und die maximale Ausfahrlänge der Markise. Das Ergebnis muss in Prozent umgerechnet werden, um die Markise fahren zu lassen.
Der Grundgedanke: es muss ein vektorieller Ansatz gefahren werden. Die Vorderkante des Balkons muss als unendliche Linie betrachtet werden, von dort muss ein Vektor aus Azimut und Elevation zur Sonne gebildet werden. Die Vorderkante der Markise muss ebenfalls als Linie betrachtet werden und es muss berechnet werden, wann diese Line beim ausfahren unter dem Ausfallwinkel den Vektor unterbricht.
Da dachte ich mir: hui, recht komplex, aber prima, ein tolles Projekt um mal zu gucken, was KIs so hergeben.
Ich habe also mehrere KIs tagelang gestresst und es kam viel Müll dabei raus. Die haben mir mit fester Überzeugung haufenweise Quatsch erzählt und ausgegeben. Aber es war sehr interessant damit zu arbeiten und zu lernen wie die so "ticken". Auch Simulationen konnte man sich prima erstellen lassen.
Am Ende war Cursor die einzige KI, die mit ein paar Anpassungen vektorbasiert die Lösung brachte. Für die Simulation war Lovable ziemlich prima.Nun ist es im echten Leben implementiert, getestet und funktioniert hervorragend. Die Markise fährt zentimetergenau über die gesamte Dauer.
Da ich nirgendwo eine fertige Lösung gefunden habe, möchte ich das euch hier zur Verfügung stellen:Am Ende besteht die Komplettlösung aus 2 JavaScript (1x Sonnenwinkel berechnen und 1x die nötige Markisenposition berechnen) und einem Blockly Script zum ansteuern der Markise.
Benötigte Datenpunkte:
javascript.0.Sonnenstand.Azimut javascript.0.Sonnenstand.Elevation
0_userdata.0.Markise_Automatik.Markisenposition 0_userdata.0.Markise_Automatik.Markise_Automatik
Das Script zur Berechnung der Sonnenwinkel (die Quelle weiß ich leider nicht mehr):
const suncalc = require('suncalc'); const result = getObject("system.adapter.javascript.0"); const lat = result.native.latitude; const long = result.native.longitude; const idEle = 'Sonnenstand.Elevation'; const idAzi = 'Sonnenstand.Azimut'; createState(idEle, 0, {type: 'number', unit: '°'}); createState(idAzi, 0, {type: 'number', unit: '°'}); function Sonnenstand_berechnen () { var now = new Date(); var sunpos = suncalc.getPosition(now, lat, long); var h = sunpos.altitude * 180 / Math.PI; var a = sunpos.azimuth * 180 / Math.PI + 180; setState(idEle, Math.round(10 * h) / 10, true); setState(idAzi, Math.round(a), true); } schedule("* * * * *", Sonnenstand_berechnen); // jede Minute
Das Script zur Berechnung der benötigten Markisenposition:
// Konstanten const offsetKorrektur = 10; // Korrekturwert für die reale/praktische Anpassung in % const maxMarkisenAusfahrt = 2.95; // Die maximale Ausfahrlänge der Markise in Meter const balkonAusrichtung = 242; // Die Ausrichtung / Himmelsrichtung des Balkons in Grad const neigungswinkel = 18; // Der Neigungswinkel der Markise beim ausfahren in Grad const hMontage = 2.38; // Montagehöhe der Markise über dem Balkon in Meter const minElevation = 12; // Die Sonnenhöhe / Elevation, ab der die Markise irrelevant wird (z.B. hinter Bäumen verschwunden) in Grad. Steht die Sonne niedriger, fährt die Markise ein schedule("*/1 * * * *", function () { // Aktuelle Sonnenpositionen auslesen let azimut = getState("javascript.0.Sonnenstand.Azimut").val; let elevation = getState("javascript.0.Sonnenstand.Elevation").val; // Berechnung des minimalen Azimuts (Sonne parallel zum Balkon) let minAzimut = balkonAusrichtung - 90; // Wenn Elevation unter Minimum oder Azimut zu niedrig, Markise einfahren if (elevation < minElevation || azimut < minAzimut) { setState("0_userdata.0.Markise_Automatik.Markisenposition", 0); return; } // Winkel zwischen Balkonausrichtung und Sonne let relativeAzimut = Math.abs(azimut - balkonAusrichtung); if (relativeAzimut > 180) { setState("0_userdata.0.Markise_Automatik.Markisenposition", 0); return; } // Umrechnung in Radianten let elevationRad = elevation * Math.PI / 180; let relativeAzimutRad = relativeAzimut * Math.PI / 180; let neigungRad = neigungswinkel * Math.PI / 180; // Sonnenvektor in kartesischen Koordinaten (normalisiert) // x: Ost-West // y: Nord-Süd // z: Höhe let sonnenVektor = { x: Math.cos(elevationRad) * Math.sin(relativeAzimutRad), y: Math.cos(elevationRad) * Math.cos(relativeAzimutRad), z: Math.sin(elevationRad) }; // Berechnung der benötigten Ausfahrlänge durch Schnittpunkt des Sonnenvektors // mit der geneigten Markisenebene let schnittFaktor = hMontage / (sonnenVektor.z + Math.tan(neigungRad) * sonnenVektor.y); let benoetigteAusfahrlaenge = Math.abs(schnittFaktor * sonnenVektor.y); // Begrenzung auf maximale Ausfahrlänge benoetigteAusfahrlaenge = Math.min(Math.max(0, benoetigteAusfahrlaenge), maxMarkisenAusfahrt); // Umrechnung in Prozent und Rundung let markisenPosition = Math.round((benoetigteAusfahrlaenge / maxMarkisenAusfahrt) * 100); // Offset-Korrektur hinzufügen und auf gültige Prozentwerte begrenzen markisenPosition = Math.min(Math.max(0, markisenPosition + offsetKorrektur), 100); // Setzen der Markisenposition setState("0_userdata.0.Markise_Automatik.Markisenposition", markisenPosition); });
Das Blockly Script zum fahren der Markise. Hier wird im 5% Takt gefahren und die Automatik zurückgesetzt, wenn die Sonne zu tief steht.
Hier als Code zum importieren:<xml xmlns="https://developers.google.com/blockly/xml"> <block type="on_ext" id="d[M{].$K-os4mv.?MSd:" x="-7937" y="-4337"> <mutation xmlns="http://www.w3.org/1999/xhtml" items="2"></mutation> <field name="CONDITION">any</field> <field name="ACK_CONDITION"></field> <value name="OID0"> <shadow type="field_oid" id="AH]apV/o5,U4$jH3TJLi"> <field name="oid">0_userdata.0.Markise_Automatik.Markise_Automatik</field> </shadow> </value> <value name="OID1"> <shadow type="field_oid" id="HMZ-~kOaPMKu!NQO:69f"> <field name="oid">0_userdata.0.Markise_Automatik.Markisenposition</field> </shadow> </value> <statement name="STATEMENT"> <block type="controls_if" id="qD3^6mWBMyl*mSmH3R|F"> <value name="IF0"> <block type="logic_compare" id="*5RHvT~q1T`ryqXN]VMy"> <field name="OP">EQ</field> <value name="A"> <block type="get_value" id="ciuiV0mX:C;D$s+S0/0t"> <field name="ATTR">val</field> <field name="OID">0_userdata.0.Markise_Automatik.Markise_Automatik</field> </block> </value> <value name="B"> <block type="logic_boolean" id="Z}pQy~?R%09F|s-waDMv"> <field name="BOOL">TRUE</field> </block> </value> </block> </value> <statement name="DO0"> <block type="comment" id="F6^`S[I}+uS0=M+Y6wWN"> <field name="COMMENT">Erst wenn Balkon beschienen wird</field> <next> <block type="controls_if" id="8-.7D2w3CFpr7yuO:stY"> <value name="IF0"> <block type="logic_compare" id="8$97L@Oh:Jy73-et+`A3"> <field name="OP">GTE</field> <value name="A"> <block type="get_value" id="^T*/,8rrY*hJ0b@h*5oK"> <field name="ATTR">val</field> <field name="OID">javascript.0.Sonnenstand.Azimut</field> </block> </value> <value name="B"> <block type="math_number" id="K27sF+@OllSm%%,5STZD"> <field name="NUM">152</field> </block> </value> </block> </value> <statement name="DO0"> <block type="comment" id="]RJA7iQN:Dx,smN,:[l#"> <field name="COMMENT">Im 5% Takt fahren</field> <next> <block type="controls_if" id="gQ2/Mrz6@YW:Ym3N/+^R"> <value name="IF0"> <block type="math_number_property" id="lCMXwC6Kq^H:3T.`xg{:"> <mutation divisor_input="true"></mutation> <field name="PROPERTY">DIVISIBLE_BY</field> <value name="NUMBER_TO_CHECK"> <shadow type="math_number" id="OlH*#S%O5%;W;s4al5Zc"> <field name="NUM">0</field> </shadow> <block type="get_value" id="HHA42-CFwe*A4m0+LEAV"> <field name="ATTR">val</field> <field name="OID">0_userdata.0.Markise_Automatik.Markisenposition</field> </block> </value> <value name="DIVISOR"> <block type="math_number" id="sO4;cuoVz9%m,KRYC=Xc"> <field name="NUM">5</field> </block> </value> </block> </value> <statement name="DO0"> <block type="control" id="uJr4$3Rqe1-RqJRe)JRg"> <mutation xmlns="http://www.w3.org/1999/xhtml" delay_input="false"></mutation> <field name="OID">hm-rpc.1.00111BE9A5D588.4.LEVEL</field> <field name="WITH_DELAY">FALSE</field> <value name="VALUE"> <block type="get_value" id="VQ{Czk9bgO3-N(8XTcrf"> <field name="ATTR">val</field> <field name="OID">0_userdata.0.Markise_Automatik.Markisenposition</field> </block> </value> </block> </statement> <next> <block type="comment" id="-}Rn`FeJhKPU|-bt7DXZ"> <field name="COMMENT">Schatten - Markise einfahren und Variable zurücksetzen</field> <next> <block type="controls_if" id="}/9L3?6@_{jm*(xDKXI-"> <value name="IF0"> <block type="logic_compare" id="L-c(MDVdl#!xsz$nlGjV"> <field name="OP">LTE</field> <value name="A"> <block type="get_value" id="JJC8+~EL0K?s/8)$Ub:r"> <field name="ATTR">val</field> <field name="OID">javascript.0.Sonnenstand.Elevation</field> </block> </value> <value name="B"> <block type="math_number" id="9nYCzkWBej`bL];!-n*S"> <field name="NUM">12</field> </block> </value> </block> </value> <statement name="DO0"> <block type="control" id="]o0JlQ^PNyWnodC##s/,"> <mutation xmlns="http://www.w3.org/1999/xhtml" delay_input="false"></mutation> <field name="OID">0_userdata.0.Markise_Automatik.Markise_Automatik</field> <field name="WITH_DELAY">FALSE</field> <value name="VALUE"> <block type="logic_boolean" id=";T{Q-cJ=X/zzafvV=avi"> <field name="BOOL">FALSE</field> </block> </value> <next> <block type="control" id="#=eGTE0ogeWm;);D1wS}"> <mutation xmlns="http://www.w3.org/1999/xhtml" delay_input="false"></mutation> <field name="OID">hm-rpc.1.00111BE9A5D588.4.LEVEL</field> <field name="WITH_DELAY">FALSE</field> <value name="VALUE"> <block type="math_number" id="|bY:?e@],H5BEbXE($$U"> <field name="NUM">0</field> </block> </value> </block> </next> </block> </statement> </block> </next> </block> </next> </block> </next> </block> </statement> <next> <block type="comment" id="]%/=E7Rpfk*xc%6KpkyS"> <field name="COMMENT">Regen etc...</field> </block> </next> </block> </next> </block> </statement> </block> </statement> </block> </xml>
Vielleicht nützt das ja dem ein oder anderen, der seine Markise auch so fahren lassen möchte.
Viel Spaß!