Hi Jan,
hatte ich schon versucht, ich versuche es aber nochmal. Welchen Typ bekommt das Item? Oder bleibt es ohne Typ?
Grüße
Ankündigung
Einklappen
Keine Ankündigung bisher.
Automatische Beschattung
Einklappen
X
-
Hallo Arne,
Versuch mal bitte folgendes: Ich gehe davon aus, dass die Taster separate KNX Gruppenadressen haben. Binde die in SmartHome als Items mit enforce_updates und knx_listen (NICHT knx_cache). Diese Items werden dann bei ein Tastendruck – und nur bei Tastendruck, auch nicht bei eine SmartHome Neustart – „getriggert“.
Viele Grüße,
Jan
Einen Kommentar schreiben:
-
Hallo zusammen,
ich habe mir die Logik von offline auch eingebaut und es ist jetzt bei dem Sonnenschein schon eine schöne Sache. Ich habe rund um die Uhr immer irgendeinen Zustand, der von der Logik hergenommen werden kann.
Mein einziges Problem ist, wenn man morgens die Rollladen früher oben haben möchte, sie natürlich innerhalb kurzer Zeit wieder runterfahren, denn die Logik checkt alle 10 Minuten, was gemacht werden soll. Ich bräuchte also dringend so was...
Ich scheitere aber daran, in der getriggerten Logik zu unterscheiden, von woher de Impuls für den Rollladen kam. Überwacht wird zuzeit die Rollladenposition, das Fahren und das Stoppen. Ich habe schon Versuche mit trigger['by'], trigger['source'] und changed_by() gemacht. Am erfolgversprechendsten finde ich noch changed_by().Zitat von Onkelandy Beitrag anzeigenIch hab die Manuell-Situation so bei mir gelöst, dass die Logik zuerst mal checkt, von wem die Jalousie als Letztes aktualisiert worden ist. Wenn das nicht durch die Logik geschehen ist, dann wohl durch die Visu oder KNX Taster. Dann soll bis zur Dunkelheit (theoretisch auch nach Ablauf von einer gewissen Zeit) die Jalousie unangetastet bleiben. Das spart letztlich, für jede einzelne Jalousie ein Aktiv-Item anzulegen und abzufragen.
Allerdings bekomme ich schon beim Start von smarthome.py durch das Setzen der Werte Probleme. Changed_by sagt dann mal "Cache" und mal "Init". Es kommt beim Start aber auch manchmal "knx", was ja eigentlich sonst nur bei Tasterdruck kommt. Es liegt wohl daran, dass sich nach dem Initiieren der Werte die Position mit irgendwelchen Statuswerten meldet.
Beim Tasterdruck kommt mehrheitlich "knx", aber bei manchen Ereignissen auch "Init".
Beim Triggern durch die Logic kommt mehrheitlich "Logic", dass würde ja helfen, aber in der Folge kommen dann auch wieder "knx"-Meldungen.
Lediglich ein Ereignis von der Visu kann ich klar indentifizieren.Bei dem Ansatz von JanT schein ja nur die Visu eine Rolle zu spielen, aber die nutzen wir fast nie.
Ich weiss, dass ich für eine detaillierte Analyse die Logs etc mitschicken müsste. Ich wollte aber vorab mal fragen, ob es vielleicht einen ganz anderen Ansatz gibt, um herauszufinden, woher der initiale Befehl kommt. Ideal wäre auch, wenn die Logik durch eine Rollladenbewegung mehrfach getriggert würde (Fahren, Stopp, Position)
Vielen Dank
Arne
Einen Kommentar schreiben:
-
Hallo offline,
ich möchte meinen momentanen Stand basierend auf der Logik-Variante hier beschreiben. Es ist ein totes Pferd und dürfte spätestens wenn Du die Plugin-Version veröffentlichst keine Anwendung mehr finden. Ich spare mir deshalb das weitere Finetunen und Robustmachen der Logik gegenüber „Fehlkonfiguration“ . Ich würde mich freuen, wenn wir es schafften, dem Plugin die gleichen Fähigkeiten zu geben und würde die Zeit lieber in das Finetunen der Plugin-Variante investieren.
Meine Ansätze für die Beschattungssteuerung, die mit der Raffstoreautomatik zusammen mit weiteren Logiken umgesetzt sind, sind die folgenden:
1. Tagesablauf und Raum-Trigger
Die Raffstore (bzw. Lamellen) werden in Abhängigkeit von Raumparametern gefahren: Anwesenheit, Kunstlicht an, Heizungsbedarf und Raummodus (Nacht/ Aufstehzeit/ Tag/Zubettgehen). Wenn niemand im Raum ist, wird je nach Heiz- oder Kühlbedarf und aktueller Solarstrahlung beschattet oder die Sonne eingelassen. Anwesenheit hat immer Priorität und ich habe unter Verwendung weiterer Items eine - für mich zufriedenstellende- Ansteuerung implementiert, siehe Beispiele in der beigefügten beschattungsautomatik.conf. Der grobe Tagesablauf bei Anwesenheit ist wie folgt: Ab dem Weckerklingeln bis zu einer halben Stunde Schonzeit später (Raum: Betriebsmodus = 1) werden die Lamellen nur eingeschränkt geöffnet (Beschattung). Danach wird, wenn möglich, die Sicht nach außen ermöglicht (Lamellenstellung 0 = gerade). Sollte die Sonne zu stark hineinscheinen, wird beschattet. Wenn es draußen ausreichend hell ist, kann man nicht in die Fenster gucken und ein Sichtschutz wird nur bei Einbruch der Dunkelheit oder, wenn das Licht im Raum angeschaltet wird (Kunstlicht = True) aktiviert. In Betriebsmodus = 3 (Zubettgehen), falls jemand halbnackt durch die Räume läuft, wird Sichtschutz auch aktiviert.
Ein Sonderfall ist der globale Modus „Putzen“. Wenn aktiviert, werden - wenn der Raum betreten wird - die Lamellen in Richtung der Sonne gestellt, um möglichst viel Licht einzulassen (Lamellenwinkel = hell). Der Modus wird erst verlassen, wenn der globale Modus „Putzen“ wieder deaktiviert wird.
Das Hoch- und Runterfahren der Raffstore versuche ich zu vermeiden und habe mich für folgende Variante entschieden: Im Winter bzw. in der Heizperiode darf die Automatik die Raffstore in den Wohnräumen hochfahren, wenn der Raum Heizbedarf hat und die Sonnenstrahlung hoch ist. Wieder herunterfahren wird die Automatik die Raffstore nicht. Im Sommer darf die Automatik die Raffstore entsprechend bei Bedarf herunterfahren.
Um die Antwortzeiten klein zu halten, habe ich ein raumweises Abarbeiten der Steuerung implementiert.
Eine allgemeine Implementation der Bedingungen über item_*, min_, max_* usw. wäre sicherlich sinnvoll, um die Flexibilität der Logik zu erhöhen.
2. Mit einer Logik Raffstore und Markisen steuern
Wir haben ein Satteldach und Dachfenster. Entsprechend gibt es nicht nur Raffstore sondern auch Markisen, die ich mit derselben Logik ansteuere. Deshalb auch mein Vorschlag zur Namensänderung von Raffstoreautomatik zu Beschattungsautomatik
Um Markisen und Rollläden ansteuern zu können, habe ich den Konfigurationspunkt „Position“ der item.conf in Behanghöhe und Lamellenwinkel aufgeteilt.
3. Optimale Lamellenwinkel für Blendschutz
Durch die Angabe von Lamellenwinkel = beschatten in der item.conf werden die Lamellen so angesteuert, dass eine Linie von der Lamellenhinterkante durch die Vorderkante der darüberliegenden Lamelle in Richtung Sonne zeigt. Dadurch wird das Sonnenlicht gerade so abgeblockt und der Raum trotzdem nicht abgedunkelt, da Streulicht durchkommen kann.
Die Berechnung der Lamellenwinkel für Blendschutz wird global einmal pro Logik-Aufruf unter die Annahme durchgeführt, dass die Sonne direkt vor dem Fenster steht (Azimut). Das ist eine konservative Annahme und man könnte bei seitlicher Sonne die Lamellen noch weiter öffnen. Die Lamellengeometrie, Blatttiefe und Abstand zwischen zwei Lamellen werden in der Logik angegeben.
Die Berechnung für jedes Fenster zu wiederholen, wäre übertrieben. Es würde reichen, einmal je Fensterausrichtung die Berechnung durchzuführen.
4. Freigaben zurücknehmen bei manueller Bedienung
Wenn ein Raffstore manuell über die Visu bedient wird, hat das ja meist einen Grund und man möchte nicht, dass die Automatik es wenige Minuten später rückgängig macht. Ich habe dafür eine separate Logik implementiert, die auf Änderungen der Behanghöhe oder Lamellenwinkel über die Visu lauscht und dann die Freigabe für die betroffenen Raffstore aufhebt. Siehe beigelegter Quellcode „beschattungsautomatik_freigaben.py“. Dabei werden noch Freigaben raumübergreifend und global (alle Raffstore und Markisen) verwaltet. Die Raumfreigabe bleibt per Definition aktiv bis keine der Raffstore oder Markisen mehr eine Freigabe hat oder die Raumfreigabe selbst getriggert wird. Die globale Freigabe ist nur zum Setzen oder Aufheben der Freigabe. Dessen Status ist nicht definiert.
Ich habe den Namen des Items von Aktiv auf Freigabe geändert, bestehe aber nicht drauf.
5. Heizen/ Kühlen
Wir Heizen mit Erdwärme und nutzen die Möglickeit der passive Kühlung im Sommer (nur Zirkulation, der Kompressor läuft nicht). Entsprechend ist die „Heizungsanlage“ immer aktiv. Im Winter wird Energie aufgewandt für das Heizen und im Sommer für das Kühlen. Die Raffstore unterstützen, indem raumweise bedarfsgerecht beschattet oder offen gelassen wird. Die Entscheidung ob beschattet oder die Lamellen die Sonne durchlassen sollen, wird über zwei kleine Logiken ausgeführt, siehe Anhang „heizung_tagesvoraussage.py“ und „beschattung_momentanbedarf.py“. Morgens um 06:00 wird nachgeschaut, ob in den letzten 24 Stunden der thermoelektrische Stellantrieb für den Raum offen war. Wenn ja, wurde Energie für die Klimatisierung aufgewandt. Ist die Anlage im Heizbetrieb war es fürs Heizen, sonst fürs Kühlen und die Beschattungsstrategie für den Tagesanfang steht fest. War der thermoelektrische Stellantrieb nicht offen, wird weiter geschaut: Zusätzlich zu einer Solltemperatur, stellen wir einen Temperaturbereich ein. In Räumen wo das Einhalten der Temperatur wichtig ist , wird ein kleiner Temperaturbereich eingestellt, sonst halt größer. Ist die durchschnittliche Temperatur der letzten 24h höher als die obere Grenze des Temperaturbereichs, wird für heute erstmal gekühlt werden. Wenn der Temperaturbereich unterschritten ist, entsprechend geheizt. Liegt die Durchschnittstemperatur innerhalb des definierten Temperaturbereichs wird anhand des Tagesmaximum und -minmum entschieden.
Danach wird im Tagesverlauf zweimal pro Stunde die Temperatur überprüft und das Heizen oder Kühlen über die Raffstore ggf. neu gesetzt.
6. Kalibrierung der Lamellenwinkel
Wir nutzen zwar SMI-Antriebe für die Raffstore, wodurch die Wiederholgenauigkeit des Ansteuerns ziemlich gut ist; allerdings hat der Fensterbauer oder Raffstorehersteller im Werk „gepfuscht“, so dass die Unterschiede zwischen den Raffstoren bis zu 15 Grad beträgt. Um das auszugleichen, habe ich in einer separaten Logik eine individuelle Kalibrierung der Lamellenwinkel ermöglicht.
Die Ausgabe der Lamellenwinkel wie auch die Angaben in „item.conf „habe ich auf Grad (statt Aktorwert) umgestellt. Den Ausgabentyp habe ich auf „str“ gesetzt, die das Anfahren der Extremposition wie ’unten’, ’kuehlen’ oder auch ’-90’ erlaubt.
An dieser Logik bastele ich noch. Ich möchte eine transparente Umrechnung zwischen Aktorwert (Byte) und Winkel (Fließzahl). Wenn der eine geändert wird, soll der zweite sich auch ändern. Das Problem ist der „Triggerhagel“ schon beim Hochfahren wie auch beim Ausführen der Logik. Als einfache Logik habe ich die Funktion nicht reentrant hingekriegt und es kommt zur Vertauschung der Werte. Ich werde versuchen, die Funktionalität als Plugin zu implementieren.
7. Beschattungssteuerung bei Alarm
Das Auffahren und Sperren der Raffstore und Markisen bei Alarm oder beim Fensteröffnen ist am besten auf der unteren Ebene, sprich KNX aufgehoben.
Viele Grüße,
Jan
logic.conf:
heizung_tagesvoraussage.py:Code:# logic.conf [heizung_tagesvoraussage] filename = heizung_tagesvoraussage.py crontab = 0 6 * * [beschattung_momentanbedarf] filename = beschattung_momentanbedarf.py crontab = 30 * * * [beschattungsautomatik_freigaben] filename = beschattungsautomatik_freigaben.py watch_item = globals.beschattung.automatik.freigabe | *.*.beschattung.automatik.freigabe | *.*.raffstore.*.automatik.freigabe | *.*.markise.*.automatik.freigabe | *.*.raffstore.*.lamellenwinkel_aktor | *.*.raffstore.*.behangposition_aktor | *.*.markise.*.behangposition_aktor crontab = init = init [beschattungsautomatik] filename = beschattungsautomatik.py watch_item = *.*.beschattung.automatik.freigabe | *.*.markise.automatik.freigabe | og.*.betriebsmodus | *.*.anwesendheit | *.*.beleuchtung.aktiv | Wetter.helligkeit.sw | globals.putzen cycle = 300
beschattung_momentanbedarf.py:Code:#!/usr/bin/env python3 kuehlen = 0 heizen = 1 # Berechnung für alle Items durchführen, die ein Subitem "heizung.bedarf_kuehlen_heizen" haben items = sh.match_items('*.heizung.bedarf_kuehlen_heizen') for item in items: heizungsitem = item.return_parent() logger.debug("item:{}".format(heizungsitem)) T_soll = heizungsitem.soll() T_range = heizungsitem.temperaturbereich() max_ventilstellung = heizungsitem.stellwert.db('max', '24h') if max_ventilstellung > 0.0: heizungsitem.bedarf_kuehlen_heizen(sh.globals.heizung.kuehlen_heizen()) else: T_avg = heizungsitem.ist.db('avg', '24h') if T_avg > (T_soll + T_range): heizungsitem.bedarf_kuehlen_heizen(kuehlen) elif (T_avg) < (T_soll - T_range): heizungsitem.bedarf_kuehlen_heizen(heizen) else: T_min = heizungsitem.ist.db('min', '24h') T_max = heizungsitem.ist.db('max', '24h') if T_min < (T_soll - T_range) and T_max < (T_soll + T_range): heizungsitem.bedarf_kuehlen_heizen(heizen) elif T_max > (T_soll + T_range) and T_min > (T_soll - T_range): heizungsitem.bedarf_kuehlen_heizen(kuehlen) else: pass #unverändert
beschattungsautomatik_freigaben.py:Code:#!/usr/bin/env python3 # Tagesvoraussagen aus_ heizung_tagesvoraussage.py kuehlen = 0 heizen = 1 # Berechnung für alle Items durchführen, die ein Subitem "heizung.bedarf_kuehlen_heizen" haben items = sh.match_items('*.heizung.bedarf_kuehlen_heizen') for item in items: heizungsitem = item.return_parent() T_soll = heizungsitem.soll() T_range = heizungsitem.temperaturbereich() if heizungsitem.bedarf_kuehlen_heizen() == heizen: if heizungsitem.ist() > (T_soll + T_range): heizungsitem.bedarf_kuehlen_heizen(kuehlen) else: if heizungsitem.ist() < (T_soll - T_range): heizungsitem.bedarf_kuehlen_heizen(heizen)
beschattungsautomatik.conf:Code:#!/usr/bin/env python3 # trigger from: globals.beschattung.automatik.freigabe | *.[room].beschattung.automatik.freigabe | *.[room].raffstore/markise.*.automatik.freigabe | *.[room].raffstore.*.lamellenwinkel_aktor | *.[room].raffstore/markise.*.behangposition_aktor def test_room_enable(sh, room): items = sh.match_items("*.{0}.raffstore.*.automatik.freigabe".format(room)) freigabe_item = sh.match_items("*.{0}.beschattung.automatik.freigabe".format(room))[0] one_enabled = False all_enabled = True for item in items: one_enabled = one_enabled or item() all_enabled = all_enabled and item() items = sh.match_items("*.{0}.markise.*.automatik.freigabe".format(room)) for item in items: one_enabled = one_enabled or item() all_enabled = all_enabled and item() if not one_enabled: freigabe_item = sh.match_items("*.{0}.beschattung.automatik.freigabe".format(room))[0] if freigabe_item() != False: freigabe_item(False) if all_enabled: freigabe_item = sh.match_items("*.{0}.beschattung.automatik.freigabe".format(room))[0] if freigabe_item() != True: freigabe_item(True) # Globale Freigabe if trigger['source'] == 'globals.beschattung.automatik.freigabe': # Feigabe für alle Raffstore Items setzen #all_items_set = set(sh.match_items('*raffstore.*.automatik.freigabe')) #items = all_items_set.difference(sh.match_items('*raffstore.alle.automatik.freigabe')) items = sh.match_items('*.*.beschattung.automatik.freigabe') for item in items: item(trigger['value']) exit() # Init if ('Scheduler' in trigger['by']) or ('None' in trigger['by']): for item in sh.match_items("*.*.beschattung.automatik.freigabe"): room = str(item).split('.')[1] test_room_enable(sh, room) exit() # Test von CLI if 'CLI' in trigger['by']: room = 'schlafen' else: room = str(trigger['source']).split('.')[1] # Raumweise Freigabe if (trigger['source'] in str(sh.match_items('*.*.beschattung.automatik.freigabe'))): # Feigabe für alle Raffstore und Markisen in 'room' setzen items = sh.match_items("*.{0}.raffstore.*.automatik.freigabe".format(room)) for item in items: item(trigger['value']) items = sh.match_items("*.{0}.markise.*.automatik.freigabe".format(room)) for item in items: item(trigger['value']) elif (trigger['source'] in str(sh.match_items('*.*.raffstore.alle.*'))) and ('Visu' in sh.match_items(trigger['source'])[0].changed_by()): # Eine oder alle (im Raum) Raffstore wurde über die Visu bewegt: Automatik Freigabe zurücknehmen freigabe_item = sh.match_items("*.{0}.beschattung.automatik.freigabe".format(room))[0] if freigabe_item() != False: freigabe_item(False) elif (trigger['source'] in str(sh.match_items('*.*.markise.alle.*'))) and ('Visu' in sh.match_items(trigger['source'])[0].changed_by()): # Eine oder alle (im Raum) Raffstore wurde über die Visu bewegt: Automatik Freigabe zurücknehmen freigabe_item = sh.match_items("*.{0}.beschattung.automatik.freigabe".format(room))[0] if freigabe_item() != False: freigabe_item(False) elif (trigger['source'] in str(sh.match_items('*.*.raffstore.*.behangposition_aktor')) or (trigger['source'] in str(sh.match_items('*.*.raffstore.*.lamellenwinkel_aktor')))) and ('Visu' in sh.match_items(trigger['source'])[0].changed_by()): raffstore = str(trigger['source']).split('.')[3] logger.debug("Hebe Freigabe auf, aufgrund: {0}, trigger by:{1}".format(trigger['source'],sh.match_items(trigger['source'])[0].changed_by() )) sh.match_items("*.{0}.raffstore.{1}.automatik.freigabe".format(room, raffstore))[0](False) elif (trigger['source'] in str(sh.match_items('*.*.markise.*.behangposition_aktor'))) and ('Visu' in sh.match_items(trigger['source'])[0].changed_by()): markise = str(trigger['source']).split('.')[3] logger.debug("Hebe Freigabe auf, aufgrund: {0}, trigger by:{1}".format(trigger['source'],sh.match_items(trigger['source'])[0].changed_by() )) sh.match_items("*.{0}.markise.{1}.automatik.freigabe".format(room, markise))[0](False) elif (trigger['source'] in str(sh.match_items('*.*.raffstore.*.freigabe')) or (trigger['source'] in str(sh.match_items('*.*.markise.*.freigabe')))): # wenn für den Raum keine Freigaben mehr aktiv sind, dann setzte Freigabe für den Raum zurück test_room_enable(sh, room)
beschattungsautomatik.py:Code:[globals] [[beschattung]] [[[automatik]]] [[[[freigabe]]]] type = bool enforce_updates = true [og] [[bad]] [[[beschattung]]] [[[[automatik]]]] [[[[[freigabe]]]]] type = bool enforce_updates = true [[[raffstore]]] [[[[so_links]]]] [[[[[automatik]]]]] item_helligkeit = Wetter.helligkeit.so item_anwesendheit = og.bad.anwesendheit item_heizungsbedarf = og.bad.heizung.bedarf_kuehlen_heizen item_kunstlicht = og.bad.beleuchtung.aktiv item_raummodus = og.bad.betriebsmodus [[[[[[putzen]]]]]] name = Putzen anwesendheit = True putzen = True leave_putzen = False lamellenwinkel = hell ## Tageszyklus mit Anwesendheit [[[[[[morgen_daemmerung_anwesend]]]]]] name = Dämmerung am Morgen Anwesend anwesendheit = True min_helligkeit = 5 raummodus = 1 lamellenwinkel = beschatten [[[[[[tag_anwesend]]]]]] name = Tag Anwesend anwesendheit = True min_helligkeit = 20000 kunstlicht = False raummodus = 2 max_sun_altitude = 41 min_sun_azimut = 45 max_sun_azimut = 225 lamellenwinkel = beschatten [[[[[[tag_anwesend_aussen_hell]]]]]] name = Tag Anwesend Hell ohne Sichtshutz anwesendheit = True min_helligheit = 500 kunstlicht = False raummodus = 2 lamellenwinkel = 0 [[[[[[tag_anwesend_mit_sichtshutz]]]]]] name = Tag Anwesend Hell mit Sichtschutz anwesendheit = True min_helligheit = 500 kunstlicht = True raummodus = 2 lamellenwinkel = 90 [[[[[[abend_daemmerung_anwesend]]]]]] name = Dämmerung Abend Anwesend anwesendheit = True min_helligkeit = 300 raummodus = 2 lamellenwinkel = 90 [[[[[[abend_dunkel_dunkel_anwesend]]]]]] name = Abend Dunkel Anwesend anwesendheit = True max_helligkeit = 300 raummodus = 2 kunstlicht = False lamellenwinkel = 0 [[[[[[abend_dunkel_hell]]]]]] name = Abend Dunkel Anwesend max_helligkeit = 300 raummodus = 2 kunstlicht = True lamellenwinkel = 90 [[[[[[nacht_anwesend]]]]]] name = Nacht Anwesend anwesendheit = True raummodus = 0 hoehe = 100 lamellenwinkel = 90 [[[[[[default_anwesend]]]]]] name = Default Anwesend anwesendheit = True lamellenwinkel = 90 ## Tageszyklus ohne Anwesendheit [[[[[[nacht_abwesend]]]]]] name = Nacht Abwesend anwesendheit = False max_helligkeit = 5 #max_solarstrahlung = 5 #kuehlen/heizen nicht mehr relevant raummodus = 0 lamellenwinkel = 90 [[[[[[heizen_winter]]]]]] name = heizen im Winter anwesendheit = False raummodus = 2 heizperiode = True heizungsbedarf = True min_solarstrahlung = 100 min_sun_azimut = 45 max_sun_azimut = 225 #hoehe = 0 lamellenwinkel = heizen [[[[[[heizen]]]]]] name = heizen anwesendheit = False heizungsbedarf = True min_solarstrahlung = 50 min_sun_azimut = 45 max_sun_azimut = 225 #hoehe = auto #hoehe nie verstellen? lamellenwinkel = heizen [[[[[[kuehlen_sommer]]]]]] name = Kühlen im Sommer anwesendheit = False heizperiode = False heizungsbedarf = False min_solarstrahlung = 600 min_sun_azimut = 45 max_sun_azimut = 225 hoehe = 100 lamellenwinkel = kuehlen [[[[[[kuehlen]]]]]] name = Kühlen anwesendheit = False heizungsbedarf = False min_solarstrahlung = 50 min_sun_azimut = 45 max_sun_azimut = 225 #hoehe = auto #hoehe nie verstellen? lamellenwinkel = kuehlen [[[[[[default_abwesend]]]]]] name = Default Abwesend anwesendheit = False lamellenwinkel = 0 [[[[[[freigabe]]]]]] type = bool value = 1 cache = on [[[[[[letzte_position_id]]]]]] type = str cache = on [[[[[[letzte_position_name]]]]]] type = str cache = on [[schlafen]] [[[beschattung]]] [[[[automatik]]]] [[[[[freigabe]]]]] type = bool enforce_updates = true [[[raffstore]]] [[[[sw]]]] [[[[[automatik]]]]] item_helligkeit = Wetter.helligkeit.sw item_anwesendheit = og.schlafen.anwesendheit item_heizungsbedarf = og.schlafen.heizung.bedarf_kuehlen_heizen item_kunstlicht = og.schlafen.beleuchtung.aktiv item_raummodus = og.schlafen.betriebsmodus [[[[[[putzen]]]]]] name = Putzen anwesendheit = True putzen = True leave_putzen = False lamellenwinkel = hell ## Tageszyklus mit Anwesendheit [[[[[[morgen_daemmerung_anwesend]]]]]] name = Dämmerung am Morgen Anwesend anwesendheit = True min_helligkeit = 5 raummodus = 1 lamellenwinkel = beschatten [[[[[[tag_anwesend]]]]]] name = Tag Anwesend anwesendheit = True min_helligkeit = 20000 kunstlicht = False raummodus = 2 max_sun_altitude = 41 min_sun_azimut = 135 max_sun_azimut = 315 lamellenwinkel = beschatten [[[[[[tag_anwesend_aussen_hell]]]]]] name = Tag Anwesend Hell ohne Sichtshutz anwesendheit = True min_helligheit = 500 kunstlicht = False raummodus = 2 lamellenwinkel = 0 [[[[[[tag_anwesend_mit_sichtshutz]]]]]] name = Tag Anwesend Hell mit Sichtschutz anwesendheit = True min_helligheit = 500 kunstlicht = True raummodus = 2 lamellenwinkel = 90 [[[[[[abend_daemmerung_anwesend]]]]]] name = Dämmerung Abend Anwesend anwesendheit = True min_helligkeit = 300 raummodus = 2 lamellenwinkel = 90 [[[[[[abend_dunkel_dunkel_anwesend]]]]]] name = Abend Dunkel Anwesend anwesendheit = True max_helligkeit = 300 raummodus = 2 kunstlicht = False lamellenwinkel = 0 [[[[[[abend_dunkel_hell]]]]]] name = Abend Dunkel Anwesend max_helligkeit = 300 raummodus = 2 kunstlicht = True lamellenwinkel = 90 [[[[[[abens_anwesend_kunstlicht]]]]]] name = Zubettgehen (Abend) mit Kunstlicht anwesendheit = True kunstlicht = True raummodus = 3 lamellenwinkel = -90 [[[[[[abens_anwesend]]]]]] name = Zubettgehen (Abend) ohne Kunstlicht anwesendheit = True kunstlicht = False raummodus = 3 lamellenwinkel = 0 [[[[[[nacht_anwesend]]]]]] name = Nacht Anwesend anwesendheit = True raummodus = 0 hoehe = 100 lamellenwinkel = -90 [[[[[[default_anwesend]]]]]] name = Default Anwesend anwesendheit = True lamellenwinkel = 90 ## Tageszyklus ohne Anwesendheit [[[[[[nacht_abwesend]]]]]] name = Nacht Abwesend anwesendheit = False max_helligkeit = 5 raummodus = 0 lamellenwinkel = -90 [[[[[[heizen_winter]]]]]] name = heizen im Winter anwesendheit = False raummodus = 2 heizperiode = True heizungsbedarf = True min_solarstrahlung = 100 min_sun_azimut = 135 max_sun_azimut = 315 hoehe = 0 lamellenwinkel = heizen [[[[[[heizen]]]]]] name = heizen anwesendheit = False heizungsbedarf = True min_solarstrahlung = 50 min_sun_azimut = 135 max_sun_azimut = 315 #hoehe = auto #hoehe nie verstellen? lamellenwinkel = heizen [[[[[[kuehlen_sommer]]]]]] name = Kühlen im Sommer anwesendheit = False heizperiode = False heizungsbedarf = False min_solarstrahlung = 600 min_sun_azimut = 135 max_sun_azimut = 315 hoehe = 100 lamellenwinkel = kuehlen [[[[[[kuehlen]]]]]] name = Kühlen anwesendheit = False heizungsbedarf = False min_solarstrahlung = 50 min_sun_azimut = 135 max_sun_azimut = 315 #hoehe = auto #hoehe nie verstellen? lamellenwinkel = kuehlen [[[[[[default_abwesend]]]]]] name = Default Abwesend anwesendheit = False lamellenwinkel = 0 [[[[[[freigabe]]]]]] type = bool value = 1 cache = on [[[[[[letzte_position_id]]]]]] type = str cache = on [[[[[[letzte_position_name]]]]]] type = str cache = on [[[markise]]] [[[[so_unten]]]] [[[[[automatik]]]]] item_helligkeit = Wetter.helligkeit.so item_anwesendheit = og.schlafen.anwesendheit item_heizungsbedarf = og.schlafen.heizung.bedarf_kuehlen_heizen item_raummodus = og.schlafen.betriebsmodus item_kunstlicht = og.schlafen.beleuchtung.aktiv [[[[[[putzen]]]]]] name = Putzen putzen = True hoehe = 0 ## Tageszyklus mit/ohne Anwesendheit [[[[[[blendenschutz]]]]]] name = Blendenschutz raummodus = 2 min_helligkeit = 20000 hoehe = 100 [[[[[[nacht]]]]]] name = Nacht raummodus = 0 hoehe = 100 [[[[[[heizen]]]]]] name = Heizen raummodus = 2 min_solarstrahlung = 200 heizungsbedarf = True min_sun_azimut = 45 max_sun_azimut = 225 hoehe = 0 [[[[[[kuehlen]]]]]] name = Kühlen raummodus = 2 min_solarstrahlung = 200 heizungsbedarf = False min_sun_azimut = 45 max_sun_azimut = 225 hoehe = 100 [[[[[[freigabe]]]]]] type = bool value = 1 cache = on [[[[[[letzte_position_id]]]]]] type = str cache = on [[[[[[letzte_position_name]]]]]] type = str cache = on [[wohnen]] [[[beschattung]]] [[[[automatik]]]] [[[[[freigabe]]]]] type = bool enforce_updates = true [[[raffstore]]] [[[[sw_links]]]] [[[[[automatik]]]]] item_helligkeit = Wetter.helligkeit.sw item_anwesendheit = eg.wohnen.anwesendheit item_heizungsbedarf = eg.wohnen.heizung.bedarf_kuehlen_heizen item_kunstlicht = eg.wohnen.beleuchtung.aktiv item_raummodus = eg.wohnen.betriebsmodus [[[[[[putzen]]]]]] name = Putzen anwesendheit = True putzen = True leave_putzen = False lamellenwinkel = hell ## Tageszyklus mit Anwesendheit [[[[[[morgen_daemmerung_anwesend]]]]]] name = Dämmerung am Morgen Anwesend anwesendheit = True min_helligkeit = 5 raummodus = 1 lamellenwinkel = beschatten [[[[[[tag_anwesend]]]]]] name = Tag Anwesend anwesendheit = True min_helligkeit = 20000 kunstlicht = False raummodus = 2 max_sun_altitude = 41 min_sun_azimut = 135 max_sun_azimut = 315 lamellenwinkel = beschatten [[[[[[tag_anwesend_aussen_hell]]]]]] name = Tag Anwesend Hell ohne Sichtshutz anwesendheit = True min_helligheit = 500 kunstlicht = False raummodus = 2 lamellenwinkel = 0 [[[[[[tag_anwesend_mit_sichtshutz]]]]]] name = Tag Anwesend Hell mit Sichtschutz anwesendheit = True min_helligheit = 500 kunstlicht = True raummodus = 2 lamellenwinkel = 90 [[[[[[abend_daemmerung_anwesend]]]]]] name = Dämmerung Abend Anwesend anwesendheit = True min_helligkeit = 300 raummodus = 2 lamellenwinkel = 90 [[[[[[abend_dunkel_dunkel_anwesend]]]]]] name = Abend Dunkel Anwesend anwesendheit = True max_helligkeit = 300 raummodus = 2 kunstlicht = False lamellenwinkel = 0 [[[[[[abend_dunkel_hell]]]]]] name = Abend Dunkel Anwesend max_helligkeit = 300 raummodus = 2 kunstlicht = True lamellenwinkel = 90 [[[[[[nacht_anwesend]]]]]] name = Nacht Anwesend anwesendheit = True raummodus = 0 lamellenwinkel = -90 [[[[[[default_anwesend]]]]]] name = Default Anwesend anwesendheit = True lamellenwinkel = 90 ## Tageszyklus ohne Anwesendheit [[[[[[nacht_abwesend]]]]]] name = Nacht Abwesend anwesendheit = False max_helligkeit = 5 raummodus = 0 lamellenwinkel = -90 [[[[[[heizen_winter]]]]]] name = heizen im Winter anwesendheit = False raummodus = 2 heizperiode = True heizungsbedarf = True min_solarstrahlung = 100 min_sun_azimut = 135 max_sun_azimut = 315 hoehe = 0 lamellenwinkel = heizen [[[[[[heizen]]]]]] name = heizen anwesendheit = False heizungsbedarf = True min_solarstrahlung = 50 min_sun_azimut = 135 max_sun_azimut = 315 #hoehe = auto #hoehe nie verstellen? lamellenwinkel = heizen [[[[[[kuehlen_sommer]]]]]] name = Kühlen im Sommer anwesendheit = False heizperiode = False heizungsbedarf = False min_solarstrahlung = 600 min_sun_azimut = 135 max_sun_azimut = 315 hoehe = 100 lamellenwinkel = kuehlen [[[[[[kuehlen]]]]]] name = Kühlen anwesendheit = False heizungsbedarf = False min_solarstrahlung = 50 min_sun_azimut = 135 max_sun_azimut = 315 #hoehe = auto #hoehe nie verstellen? lamellenwinkel = kuehlen [[[[[[default_abwesend]]]]]] name = Default Abwesend anwesendheit = False lamellenwinkel = 0 [[[[[[freigabe]]]]]] type = bool value = 1 cache = on [[[[[[letzte_position_id]]]]]] type = str cache = on [[[[[[letzte_position_name]]]]]] type = str cache = on
Code:# Raffstore Automatik V2 # # ThEr081014 Initiale fertigstellung # ThEr141014 Aktivierung über Subitem "Aktiv" anstatt über Attribut "aktiv" # ThEr141014 Item für letzte Position auch unterhalb von "RaffstoreAutomatik" # ThEr141014 Zusätzliches Item für den Namen der letzten Position # JT 240415 Bugfix in Bediengunsauswertung Azimut und Uhrzeit # Verarbeitung von weitere Items für die Bediengungsauswertung - globale und raumbezogene # Raumweise Trigger und Verarbeitung möglich # Berechnung der optimale Lamellenstellung für Beschattung: kein direkter Sonneneinlass, dabei # Lamellen möglichst weit offen (Eine gedachte Linie von der Hinterkante eine Lamelle zum Vorderkante # der überliegende Lamelle zeigt in richtung Sonne) # Trennung von behanghöhe und Lamellenwinkel um auch Markisen steuern zu können # Umstellung Einheiten: Lamellenwinkel in Grad, Behanghoehe in % # Ausgabeitems als String, ermöglicht angabe der Extremstellungen # Behhangposition: auto => bei Beschattung und Kühlen Behang nach unten fahren sonnst unverändert NICHT GETSETET! # # Einheiten: # item.conf: hoehe: [%], 0% oben, 100% unten # item.conf: lamellemwinkel [°], 90° nach oben, -90° nach unten # # # ToDo: Lösche alte Werte wenn Freigabe zurückgenommen wird # class RaffstoreAutomatik: # Konstruktor def __init__(self, sh): import math logger.info("Initialisiere Raffstore-Automatik") # Daten übernehmen self.sh = sh self.item = None # Zeit ermitteln now = time.localtime() self.akt_zeit = [now.tm_hour,now.tm_min] # Position der Sonne ermitteln und in Dezimalgrad umrechnen azimut, altitude = self.sh.sun.pos() self.sun_azimut = math.degrees(float(azimut)) self.sun_altitude = math.degrees(float(altitude)) # Solarstrahlung ermitteln self.solar_radiation = sh.Wetter.davis.solarstrahlung() # Heizperiode aktiv setzen self.heizperiode = sh.globals.heizung.kuehlen_heizen() # Putzmodus ermitteln self.putzen = sh.globals.putzen() # Lamellen geometrie L = 8.4 H = 7.2 # Grenzwert Sonnenhoehe für Beschattung sun_altitude_lim = math.degrees(math.atan((2*H*L+math.sqrt((2*H*L)**2-4*L**2*H**2))/(2*L**2))) c = H**2-L**2 a = (math.tan(math.radians(self.sun_altitude)))**2+1.0 b = -2*math.tan(math.radians(self.sun_altitude))*H x = ((-b+math.sqrt(b**2-4*a*c))/(2*a))/L if(self.sun_altitude < sun_altitude_lim): self.shadow_angle = math.degrees(-math.acos(x)) else: self.shadow_angle = math.degrees(math.acos(x)) logger.info("Sonnenhöe: {0}, optimale Lamellenwinkel für Beschattung: {1} ".format(self.sun_altitude,self.shadow_angle)) if sh.trigger_source != None and (sh.trigger_source in str(sh.match_items("*.*.anwesendheit")) or sh.trigger_source in str(sh.match_items("*.*.beleuchtung.aktiv"))): # Automatik für alle Raffstore im Raum des Triggeritems durchführen, die ein Subitem "automatik" mit Subitem "freigabe = 1" haben room = sh.trigger_source.split('.')[1] logger.info("Raffstore-Automatik trigger für Raum:{0}".format(room)) items = sh.match_items("*.{0}.raffstore.*.automatik.freigabe".format(room)) for item in items: if (item() == 1): self.__run(item.return_parent()) # Automatik für alle Markisen im Raum des Triggeritems durchführen, die ein Subitem "automatik" mit Subitem "freigabe = 1" haben room = sh.trigger_source.split('.')[1] logger.info("Markisen-Automatik trigger für Raum:{0}".format(room)) items = sh.match_items("*.{0}.markise.*.automatik.freigabe".format(room)) for item in items: if (item() == 1): self.__run(item.return_parent()) else: # Automatik für alle Raffstore Items durchführen, die ein Subitem "automatik" mit Subitem "freigabe = 1" haben items = sh.match_items("*.*.raffstore.*.automatik.freigabe") logger.info("Raffstore-Automatik trigger für alle Räume") for item in items: if (item() == 1): self.__run(item.return_parent()) # Automatik für alle markisen Items durchführen, die ein Subitem "automatik" mit Subitem "freigabe = 1" haben items = sh.match_items("*.*.markise.*.automatik.freigabe") logger.info("Markisen-Automatik trigger für alle Räume") for item in items: if (item() == 1): self.__run(item.return_parent()) # Führt die Automatik für ein Raffstore-Item durch def __run(self, item): logger.info("Starte Raffstore-Automatik mit Item {0}".format(item.id())) # Daten übernehmen self.item = item.return_parent() self.config = item.conf # Items holen self.item_letzte_position_id = self.__get_child_item(item,"letzte_position_id") self.item_letzte_position_name = self.__get_child_item(item,"letzte_position_name") try: self.item_helligkeit = self.sh.return_item(self.config["item_helligkeit"]) if self.item_helligkeit == None: raise AttributeError("Das für 'item_helligkeit' angegebene Item '%s' ist unbekannt." %(self.config['item_helligkeit'])) except KeyError: self.item_helligkeit = None self.item_anwesendheit = self.sh.return_item(self.config["item_anwesendheit"]) if self.item_anwesendheit == None: raise AttributeError("Das für 'item_anwesendheit' angegebene Item '%s' ist unbekannt." %(self.config['item_anwesendheit'])) self.item_heizungsbedarf = self.sh.return_item(self.config["item_heizungsbedarf"]) if self.item_heizungsbedarf == None: raise AttributeError("Das für 'item_heizungsbedarf' angegebene Item '%s' ist unbekannt." %(self.config['item_heizungsbedarf'])) self.item_kunstlicht = self.sh.return_item(self.config["item_kunstlicht"]) if self.item_kunstlicht == None: raise AttributeError("Das für 'item_kunstlicht' angegebene Item '%s' ist unbekannt." %(self.config['item_kunstlicht'])) self.item_raummodus = self.sh.return_item(self.config["item_raummodus"]) if self.item_raummodus == None: raise AttributeError("Das für 'item_raummodus' angegebene Item '%s' ist unbekannt." %(self.config['item_raummodus'])) self.items_position = self.sh.find_children(self.item, "hoehe") self.items_position += self.sh.find_children(self.item, "lamellenwinkel") logger.info("items_position: {0}".format(self.items_position)) # Anwesendheit ermitteln try: self.anwesendheit = self.item_anwesendheit() except: self.anwesendheit = None # Relevante Helligkeit ermitteln try: self.helligkeit = self.item_helligkeit() except: self.helligkeit = None # Heizungsbedarf ermitteln try: self.heizungsbedarf = self.item_heizungsbedarf() except: self.heizungsbedarf = None # Status Kunstlicht ermitteln try: self.kunstlicht = self.item_kunstlicht() except: self.kunstlicht = None # Status Raum Betriebsmodus ermitteln try: self.raummodus = self.item_raummodus() except: self.raummodus = None # Bisherige Position ermitteln old_pos_item_id = self.item_letzte_position_id() old_pos_item = self.sh.return_item(old_pos_item_id) if old_pos_item != None and not self.__check_leave_pos_item(old_pos_item): logger.info("Position kann nicht verlassen werden") new_pos_item = old_pos_item else: # Passende Position heraussuchen new_pos_item = self.__find_pos_item() if new_pos_item == None: logger.info("Keine passende Position gefunden!") return # Position im Item "Modus" speichern new_pos_item_id = new_pos_item.id() new_pos_item_name = new_pos_item._name self.item_letzte_position_id(new_pos_item_id) self.item_letzte_position_name(new_pos_item_name) logger.info("Neue Position: '{0}' ({1})".format(new_pos_item_name,new_pos_item_id)) # Raffstoreposition aus dem Positions-Item ermitteln #position = self.__get_position_from_pos_item(new_pos_item) ret = self.__get_angle_from_pos_item(new_pos_item) angle = ret[0] anglecontrol = ret[1] height = self.__get_height_from_pos_item(new_pos_item, anglecontrol) # Raffstoreposition anfahren if height == None: return try: logger.info("Fahre auf Höhe {0:.0f}%, Lamelle {1:.1f}°".format(height, angle)) except ValueError: logger.info("Fahre auf Höhe {0}, Lamelle {1}".format(height, angle)) #Items für Raffstoresteuerung holen item_hoehe = self.__get_child_item(self.item,"behangposition_prozent") try: item_lamelle = self.__get_child_item(self.item,"lamellenwinkel_grad") logger.info("item_lamelle: {0}".format(item_lamelle)) except AttributeError: # ToDo: nur wenn Markise erlauben item_lamelle = None # Fahrbefehl für Höhe nur senden, wenn wir um mindestens 10% verändern //Attribut "hoehe" existiert und try: hoehe_delta = self.__byte2rel(item_hoehe()) - height if (abs(hoehe_delta) > 10): #item_hoehe(position[0]) item_hoehe(int(self.__rel2byte(height) + 0.5)) except: pass # Fahrbefehl für Lamelle nur, wenn "lamellenwinkel" exisitiert, der Raffstore um mindestens 10% herabgelassen ist # und die Winkeländerung > 2° ist try: if item_lamelle != None: if len(item_lamelle()) == 0: # Lamellenitem leer? logger.debug("item_lamelle: {0}".format(item_lamelle)) logger.debug("angle: {0}".format(angle)) try: winkel_delta = float(item_lamelle()) - angle if (abs(winkel_delta) >= 2 and height > 10): item_lamelle(str(int(angle + 0.5))) except (ValueError, TypeError): if angle != item_lamelle(): item_lamelle(str(angle)) else: try: winkel_delta = float(item_lamelle()) - angle if (abs(winkel_delta) >= 2 and height > 10): item_lamelle(str(int(angle + 0.5))) except (ValueError, TypeError): if angle != item_lamelle(): item_lamelle(str(angle)) except ValueError: logger.info("Fehler in Lamellentem: {0}='{1}'".format(item_lamelle, item_lamelle())) # Liest die Positionsinformationen aus einem Item und gibt Sie im Format "Liste [%Höhe,%Lamelle]" zurück def __get_position_from_pos_item(self, item): if not 'position' in item.conf: id = item.id() logger.error("Das Item '{0}' enthält kein Attribut 'position'".format(id)) return None value = item.conf['position'] if value == 'auto': return self.__get_position_from_sun() value_parts = value.split(",") if len(value_parts) != 2: id = item.id() logger.error("Das Konfigurations-Attribut '{0}' im Item '{1}' muss im Format '###, ###' angegeben werden.".format(attribute, id)) return None else: try: hoehe = int(value_parts[0]) lamelle = int(value_parts[1]) return [hoehe,lamelle] except ValueError: id = item.id() logger.error("Das Konfigurations-Attribut '{0}' im Item '{1}' muss im Format '###, ###' angegeben werden.".format(attribute, id)) return None # Liest die Positionsinformationen aus einem Item und gibt die Lamellenwinkel zurück def __get_angle_from_pos_item(self, item): if not 'lamellenwinkel' in item.conf: # lamellenwinkel nicht ändern return [None, 'None'] value = item.conf['lamellenwinkel'] if value in ['down', 'min', 'unten', 'max_down', 'max_unten', 'cooling', 'kuehlen', '-90']: return ['max_unten', 'kuehlen'] elif value == 'heizen': return [self.__get_angle_from_sun_heating(), 'heizen'] elif value == 'hell': return [self.__get_angle_from_sun_heating(), 'hell'] elif value == 'beschatten': return [self.__get_angle_from_sun_shadow(), 'beschatten'] else: try: angle = int(value) return [angle, 'None'] except ValueError: id = item.id() logger.error("Das Konfigurations-Attribut '{0}' im Item '{1}' ist Fehlerhaft.".format(attribute, id)) return None # Liest die Positionsinformationen aus einem Item und gibt die Höhe zurück def __get_height_from_pos_item(self, item, anglecontrol): if not 'hoehe' in item.conf: # hohe nicht ändern verwende jetztige Wert item_hoehe = self.__get_child_item(self.item,"behangposition_prozent") return item_hoehe() value = item.conf['hoehe'] if value == 'auto': if value in ['kuehlen', 'beschatten']: return 100 else: try: hoehe = int(value) return hoehe except ValueError: id = item.id() logger.error("Das Konfigurations-Attribut '{0}' im Item '{1}' ist Fehlerhaft.".format(attribute, id)) return None # Liefert eine Positionsangabe für den Raffstore basierend auf dem Sonnenstand # Zur Nachführung wird der Raffstore ganz heruntergefahren und versucht, # den Lamellenwinkel senkrecht zur Sonne zu stellen. def __get_position_from_sun(self): #TODO % => byte logger.info("Sonnenposition: Azimut {0} Altitude {1}".format(self.sun_azimut,self.sun_altitude)) # Raffstore senkrecht zur Sonne stellen winkel = 90-self.sun_altitude logger.info("Winkel auf {0}°".format(winkel)) # Umrechnen auf Wert (90° = 0%, 0° = 50%, -90° = 100%) prozent = 50-winkel/90*50 logger.info("Lamelle auf {0}%".format(prozent)) return [100,prozent] # Liefert ein Lamellenwinkel in % für Heizbetrieb basierend auf dem Sonnenstand def __get_angle_from_sun_heating(self): logger.info("Sonnenposition: Azimut {0} Altitude {1}".format(self.sun_azimut,self.sun_altitude)) # Lamellen richtung Sonne stellen winkel = self.sun_altitude logger.info("Winkel auf {0}°".format(winkel)) # Umrechnen auf Wert (90° = 0%, 0° = 50%, -90° = 100%) # prozent = 50-winkel/90*50 #byte = self.__deg2byte(winkel) #logger.info("Lamelle auf {0}/255".format(byte)) return winkel # Liefert ein Lamellenwinkel in % für eine optimierte Beschattung basierend auf dem Sonnenstand def __get_angle_from_sun_shadow(self): logger.info("Sonnenposition: Azimut {0} Altitude {1}".format(self.sun_azimut,self.sun_altitude)) # Lamellen einstellen so, dass gearde alles abgeschattet ist winkel = self.shadow_angle logger.info("Winkel auf {0}°".format(winkel)) return winkel # Sucht ein bestimmtes Item unterhalb eines gegebenen Items # Wenn das Item gefunden wird, wird es zurückgegeben # Wird das Item nicht gefunden, wird ein AttributeError geworfen def __get_child_item(self, item, child_id): search_id = item.id()+"."+child_id for child in item.return_children(): if child.id() == search_id: return child itemId = self.item.id() raise AttributeError("Unterhalb des Items '%s' fehlt ein Item '%s'" %(itemId, child_id)) # Loopt durch alle Positionen und liefert die erste Position zurück, bei der alle Bedingungen erfüllt sind def __find_pos_item(self): logger.info("Suche Item für Zeit = {0}, Helligkeit = {1}, Sonnenhöhe = {2:.1f}°, Sonne Azimut: {3:.1f}°, Heizperiode: {4}, Putzmodus: {5}, Anwesendheit = {6}, Heizungsbedarf = {7}, Solarstrahlung = {8}, Kunstlicht aktiv = {9}, Raummodus = {10}".format(self.akt_zeit, self.helligkeit, self.sun_altitude, self.sun_azimut, self.heizperiode, self.putzen, self.anwesendheit, self.heizungsbedarf, self.solar_radiation, self.kunstlicht, self.raummodus)) for item in self.items_position: if self.__check_enter_pos_item(item): return item return None # Prüft, ob die in einem Positions-Item erfassten Leave-Bedingungen erfüllt sind, so dass die Position wieder verlassen werden darf # position: Positions-Item mit den Bedingungen als Attribute # Rückgabe: TRUE: Position darf verlassen werden, FALSE: Position darf nicht verlassen werden def __check_leave_pos_item(self, position): id = position.id() logger.info("Prüfe ob Position '{0}' verlassen werden darf".format(id)) # Helligkeitsbedingung if 'leave_min_helligkeit' in position.conf and self.helligkeit < int(position.conf['leave_min_helligkeit']): logger.info(" -> zu dunkel") return False; if 'leave_max_helligkeit' in position.conf and self.helligkeit > int(position.conf['leave_max_helligkeit']): logger.info(" -> zu hell") return False; # Solarstrahlung if 'leave_min_solarstrahlung' in position.conf and self.solar_radiation < int(position.conf['leave_min_solarstrahlung']): logger.info(" -> Solarstrahlung zu gering") return False; if 'leave_max_solarstrahlung' in position.conf and self.solar_radiation > int(position.conf['leave_max_solarstrahlung']): logger.info(" -> Solarstrahlung zu hoch") return False; # Zeitbedingung if 'leave_min_zeit' in position.conf or 'leave_max_zeit' in position.conf: min_zeit = self.__get_time_attribute(position,"leave_min_zeit",[0,0]) max_zeit = self.__get_time_attribute(position, "leave_max_zeit", [24,00]) if self.__compare_time(min_zeit, max_zeit) != 1: # min </= max: Normaler Vergleich if self.__compare_time(self.akt_zeit, min_zeit) == -1 or self.__compare_time(self.akt_zeit, max_zeit) == 1: logger.info(" -> außerhalb der Zeit (1)") return False else: # min > max: Invertieren #if self.__compare_time(self.akt_zeit, min_zeit) == 1 and self.__compare_time(self.akt_zeit, min_zeit) == -1: if self.__compare_time(self.akt_zeit, min_zeit) == -1 and self.__compare_time(self.akt_zeit, max_zeit) == 1: logger.info(" -> außerhalb der Zeit (2)") return False # Putzbedingung if 'leave_putzen' in position.conf and self.putzen != self.__cast_bool(position.conf['leave_putzen']): logger.info(" -> Putzmodus nicht {0}".format(str(self.__cast_bool(position.conf['putzen'])))) return False; # Sonnenhöhe if 'leave_min_sun_altitude' in position.conf and self.sun_altitude < int(position.conf['leave_min_sun_altitude']): logger.info(" -> Sonne zu niedrig") return False if 'leave_max_sun_altitude' in position.conf and self.sun_altitude > int(position.conf['leave_max_sun_altitude']): logger.info(" -> Sonne zu hoch") return False # Sonnenrichtung if 'leave_min_sun_azimut' in position.conf or 'leave_max_sun_azimut' in position.conf: min_azimut = 0 max_azimut = 90 if 'leave_min_sun_azimut' in position.conf: min_azimut = int(position.conf['leave_min_sun_azimut']) if 'leave_max_sun_azimut' in position.conf: max_azimut = int(position.conf['leave_max_sun_azimut']) if min_azimut < max_azimut: if self.sun_azimut < min_azimut or self.sun_azimut > max_azimut: logger.info(" -> außerhalb der Sonnenrichtung (1)") return False; else: #if self.sun_azimut > min_azimut and self.sun_azimut < max_azimut: if self.sun_azimut < min_azimut and self.sun_azimut > max_azimut: logger.info(" -> außerhalb der Sonnenrichtung (2)") return False; # Alle Bedingungen erfüllt logger.info(" -> passt".format(position.id())); return True # Prüft, ob die in einem Positions-Item erfassten Bedingungen erfüllt sind, so dass die Position geeignet ist # position: Positions-Item mit den Bedingungen als Attribute # Rückgabe: TRUE: Position ist geeignet, FALSE: Position ist nicht geeignet def __check_enter_pos_item(self, position): id = position.id() logger.info("Prüfe ob Position '{0}' geeignet ist ".format(id)) # Anwesendheitsbedingung if 'anwesendheit' in position.conf and self.anwesendheit != self.__cast_bool(position.conf['anwesendheit']): logger.info(" -> Anwesendheit nicht {0}".format(str(self.__cast_bool(position.conf['anwesendheit'])))) return False; # Heizungsbedingung if 'heizungsbedarf' in position.conf and self.heizungsbedarf != self.__cast_bool(position.conf['heizungsbedarf']): logger.info(" -> Heizungsbedarf nicht {0}".format(str(self.__cast_bool(position.conf['heizungsbedarf'])))) return False; # Kunstlichtbedingung if 'kunstlicht' in position.conf and self.kunstlicht != self.__cast_bool(position.conf['kunstlicht']): logger.info(" ->Kunstlicht aktiv nicht {0}".format(str(self.__cast_bool(position.conf['kunstlicht'])))) return False; # Raummodusbedingung if 'raummodus' in position.conf and self.raummodus != int(position.conf['raummodus']): logger.info(" ->Raummodus nicht {0}".format(str(int(position.conf['raummodus'])))) return False; # Heizperiode aktiv if 'heizperiode' in position.conf and self.heizperiode != self.__cast_bool(position.conf['heizperiode']): logger.info(" ->Heizperiode nicht {0}".format(str(self.__cast_bool(position.conf['heizperiode'])))) return False; # Putzenbedingung if 'putzen' in position.conf and self.putzen != self.__cast_bool(position.conf['putzen']): logger.info(" ->Putzmodus nicht {0}".format(str(self.__cast_bool(position.conf['putzen'])))) return False; # Helligkeitsbedingung if 'min_helligkeit' in position.conf and self.helligkeit < int(position.conf['min_helligkeit']): logger.info(" -> zu dunkel") return False; if 'max_helligkeit' in position.conf and self.helligkeit > int(position.conf['max_helligkeit']): logger.info(" -> zu hell") return False; # Solarstrahlung if 'min_solarstrahlung' in position.conf and self.solar_radiation < int(position.conf['min_solarstrahlung']): logger.info(" -> Solarstrahlung zu gering") return False; if 'max_solarstrahlung' in position.conf and self.solar_radiation > int(position.conf['max_solarstrahlung']): logger.info(" -> Solarstrahlung zu hoch") return False; # Zeitbedingung if 'min_zeit' in position.conf or 'max_zeit' in position.conf: min_zeit = self.__get_time_attribute(position,"min_zeit",[0,0]) max_zeit = self.__get_time_attribute(position, "max_zeit", [24,00]) if self.__compare_time(min_zeit, max_zeit) != 1: # min </= max: Normaler Vergleich if self.__compare_time(self.akt_zeit, min_zeit) == -1 or self.__compare_time(self.akt_zeit, max_zeit) == 1: logger.info(" -> außerhalb der Zeit (1)") return False else: # min > max: Invertieren #if self.__compare_time(self.akt_zeit, min_zeit) == 1 and self.__compare_time(self.akt_zeit, min_zeit) == -1: if self.__compare_time(self.akt_zeit, min_zeit) == -1 and self.__compare_time(self.akt_zeit, max_zeit) == 1: logger.info(" -> außerhalb der Zeit (2)") return False # Sonnenhöhe if 'min_sun_altitude' in position.conf and self.sun_altitude < int(position.conf['min_sun_altitude']): logger.info(" -> Sonne zu niedrig") return False if 'max_sun_altitude' in position.conf and self.sun_altitude > int(position.conf['max_sun_altitude']): logger.info(" -> Sonne zu hoch") return False # Sonnenrichtung if 'min_sun_azimut' in position.conf or 'max_sun_azimut' in position.conf: min_azimut = 0 max_azimut = 90 if 'min_sun_azimut' in position.conf: min_azimut = int(position.conf['min_sun_azimut']) if 'max_sun_azimut' in position.conf: max_azimut = int(position.conf['max_sun_azimut']) if min_azimut < max_azimut: if self.sun_azimut < min_azimut or self.sun_azimut > max_azimut: logger.info(" -> außerhalb der Sonnenrichtung (1)") return False; else: #if self.sun_azimut > min_azimut and self.sun_azimut < max_azimut: if self.sun_azimut < min_azimut and self.sun_azimut > max_azimut: logger.info(" -> außerhalb der Sonnenrichtung (2)") return False; # Alle Bedingungen erfüllt logger.info(" -> passt".format(position.id())); return True # Ermittelt und prüft ein Zeit-Attribut und liefert es im Format "Liste [Stunde, Minute]" zurück def __get_time_attribute(self, item, attribute, default): if not attribute in item.conf: return default value = item.conf[attribute] value_parts = value.split(",") if len(value_parts) != 2: id = item.id() logger.error("Das Konfigurations-Attribut '{0}' im Item '{1}' muss im Format '###, ###' angegeben werden.".format(attribute, id)) else: try: stunde = int(value_parts[0]) minute = int(value_parts[1]) return [stunde,minute] except ValueError: id = item.id() logger.error("Das Konfigurations-Attribut '{0}' im Item '{1}' muss im Format '###, ###' angegeben werden.".format(attribute, id)) return default # Vergleicht zwei Zeitwerte (als Liste [Stunde, Minute]) # -1: Zeit1 < Zeit2 # 0: Zeit1 = Zeit2 # 1: Zeit 1 > Zeit 2 def __compare_time(self, zeit1, zeit2): if zeit1[0] < zeit2[0]: return -1 elif zeit1[0] > zeit2[0]: return 1 else: if zeit1[1] < zeit2[1]: return -1 elif zeit1[1] > zeit2[1]: return 1 else: return 0 def __cast_bool(self, value): if type(value) in [bool, int, float]: if value in [False, 0]: return False elif value in [True, 1]: return True else: raise ValueError elif type(value) in [str, str]: if value.lower() in ['0', 'false', 'no', 'off']: return False elif value.lower() in ['1', 'true', 'yes', 'on']: return True else: raise ValueError else: raise TypeError # Raffstore-Automatik aufrufen (Klasse instanziieren, den Rest macht der Konstruktor ...) sh.trigger_source = trigger['source'] logger.info("trigger_source: {0}".format(str(sh.trigger_source))) RaffstoreAutomatik(sh)
Einen Kommentar schreiben:
-
Ich hab die Manuell-Situation so bei mir gelöst, dass die Logik zuerst mal checkt, von wem die Jalousie als Letztes aktualisiert worden ist. Wenn das nicht durch die Logik geschehen ist, dann wohl durch die Visu oder KNX Taster. Dann soll bis zur Dunkelheit (theoretisch auch nach Ablauf von einer gewissen Zeit) die Jalousie unangetastet bleiben. Das spart letztlich, für jede einzelne Jalousie ein Aktiv-Item anzulegen und abzufragen.
Bei Einbruch der Dunkelheit werden alle Jalousie durch die Logik um 1% nach oben gefahren, somit also von der Logik getriggert und wieder im Automatikbetrieb. Letzteres ist vielleicht nicht ganz so nobel, da hat sicher wer ne feinere Lösung
Einen Kommentar schreiben:
-
Hallo,
ich habe das gelöst in dem ich in eine separate Logik auf manuelles fahren der Behanghöhen/Lamellenwinkel lausche und das Aktiv-Item dann lösche. In der Nacht wird global alle Aktiv-Item wieder gesetzt. Neuer Tag, neue Möglichkeiten...Zitat von hhhc Beitrag anzeigenWas zusätzlich noch auf der Wunschliste steht ist, das bei manueller Schaltung eines Items dieses für 2-3h von der Automatik ausgenommen wird und danach wieder in den Automatik-Modus zurück fällt.
Use case:
* Übergangszeit, Raffstoren eigentlich oben, manuelles Fahren wg Fernsehen / Mittagsschlaf auf der Couch
* Hochsommer, Kinder gehen um 19-19:30h ins Bett, es ist noch richtig hell, Raffstoren werden manuell gefahren
Viele Grüße,
Jan
Einen Kommentar schreiben:
-
Hallo hhhc,
Sowas habe ich im Plugin auch bereits eingebaut.Zitat von hhhc Beitrag anzeigen1) Steuerung der automatischen Beschattung nur, wenn die Aussentemperatur eine gewisse Höhe hat. Im Sommer soll das natürlich beschatten, aber im Winter / in der Übergangszeit soll der Sonneneinfall das Haus mit heizen.
Das habe ich bei mir so gelöst, dass ich eine GA habe, auf die alle "Aktiv"-Items zusätzlich hören.Zitat von hhhc Beitrag anzeigen2) Abschaltung der gesamten Logik mittels eines neuen Items, so dass ich nicht alle 18 Items deaktivieren muss (bei Besuch oder so)
Das ist eine gute Idee. Das lässt sich sicherlich einbauenZitat von hhhc Beitrag anzeigenWas zusätzlich noch auf der Wunschliste steht ist, das bei manueller Schaltung eines Items dieses für 2-3h von der Automatik ausgenommen wird und danach wieder in den Automatik-Modus zurück fällt.
Use case:
* Übergangszeit, Raffstoren eigentlich oben, manuelles Fahren wg Fernsehen / Mittagsschlaf auf der Couch
* Hochsommer, Kinder gehen um 19-19:30h ins Bett, es ist noch richtig hell, Raffstoren werden manuell gefahren
Grüße
offline
Einen Kommentar schreiben:
-
Deswegen hab' ich mir inzwischen nen zusätzlichen HF-Melder im Bad gegönnt. Hatte nämlich zusätzlich noch viel Spass, weil unter der Dusche immer das Licht aus ging (Winter)Zitat von JanT Beitrag anzeigenund keine war im Raum anwesend, da der Präsenzmelder die Dusche nicht erfasst!
Gruss
Jochen
Einen Kommentar schreiben:
-
Hallo offline,
ich nutze Deine Automatik seit gut einem halben Jahr und habe jetzt im Frühling auch noch die automatische Beschattung konfiguriert. Funktioniert tadellos und bin echt happy.
Ich habe kleinere Adaptionen eingebaut, die Du ggf für das Plugin berücksichtigen könntest?
1) Steuerung der automatischen Beschattung nur, wenn die Aussentemperatur eine gewisse Höhe hat. Im Sommer soll das natürlich beschatten, aber im Winter / in der Übergangszeit soll der Sonneneinfall das Haus mit heizen.
Das habe ich analog der Helligkeit implementiert.
item.conf
Code:[[[[[RaffstoreAutomatik]]]]] item_helligkeit = Aussen.wetterstation.helligkeit_sued # Item, über das die Helligkeit ermittelt wird [B]item_temperatur = Waermepumpe.Temperatur_TA[/B] # Item, über das die Temperatur ermittelt wird ... [[[[[TagNachfuehren]]]]] name = Tag (nachführen) min_helligkeit = 45000 leave_max_helligkeit = 30000 min_sun_altitude = 25 min_sun_azimut = 120 max_sun_azimut = 270 [B]min_temperatur = 16[/B] position = auto
In der logik.py habe ich an allen Stellen, wo du mit der Helligkeit arbeitest, das auf das neue Attribut erweitert.
2) Abschaltung der gesamten Logik mittels eines neuen Items, so dass ich nicht alle 18 Items deaktivieren muss (bei Besuch oder so)Code:... self.item_helligkeit = self.sh.return_item(self.config["item_helligkeit"]) self.item_temperatur = self.sh.return_item(self.config["item_temperatur"]) ... if self.item_helligkeit == None: raise AttributeError("Das für 'item_helligkeit' angegebene Item '%s' ist unbekannt." %(self.config['item_helligkeit'])) if self.item_temperatur == None: raise AttributeError("Das für 'item_temperatur' angegebene Item '%s' ist unbekannt." %(self.config['item_temperatur'])) ... usw
item.conf
logik.py (im Constructor)Code:[Raffstore] [[Automatik]] type = bool cache = on visu_acl = rw
Was zusätzlich noch auf der Wunschliste steht ist, das bei manueller Schaltung eines Items dieses für 2-3h von der Automatik ausgenommen wird und danach wieder in den Automatik-Modus zurück fällt.Code:# Check if global Raffstore automatik an oder aus if not self.sh.Raffstore.Automatik(): logger.info("Raffstore Automatik ist aus. Item: Raffstore.Automatik") exit()
Use case:
* Übergangszeit, Raffstoren eigentlich oben, manuelles Fahren wg Fernsehen / Mittagsschlaf auf der Couch
* Hochsommer, Kinder gehen um 19-19:30h ins Bett, es ist noch richtig hell, Raffstoren werden manuell gefahren
Freu mich schon auf das Plugin. Gute Arbeit!
hhhc
Einen Kommentar schreiben:
-
Hallo zusammen,
ich habe die Logik mittlerweile zu einem Plugin umgebaut, das macht programmiertechnisch ein paar Sachen einfacher. Ich bin da immer noch am Feintuning. Im Moment baue ich zusätzliche "Sperrbedingungen" ein, über die gesteuert werden kann, dass ein Zustand bis auf weiteres nicht verlassen werden kann. Wenn alles fertig ist werde ich den Wiki-Eintrag entsprechend ergänzen bzw. das Plugin anderweitig zur Verfügung stellen.Zitat von JanT Beitrag anzeigenHallo Sprocky,
offline hat einen schönen Ansatz implementiert, siehe Forumsthread Logik für Raffstore-Steuerung oder auch SmartHome Wiki. [...]
Jan
Grüße
offline
Einen Kommentar schreiben:
-
Hallo,
nach dem ich gestern zu den Lamellensteuerungen das hoch und herunterfahren der Raffstore aktiviert habe, wurden sie im Bad hochgefahren als ich unter der Dusche stand
Alle Bediengungen waren erfüllt: Es war nach dem Aufstehen, die Heizperiode ist noch aktiv, der Raum wurde gestern beheizt, die Solareinstrahlung war > 100 W/m^2, die Sonne kam von vorne und keine war im Raum anwesend, da der Präsenzmelder die Dusche nicht erfasst!
Ich debugge noch ein bisschen vor ich den Quellcode hier reinsetze...
Viele Grüße,
Jan
Einen Kommentar schreiben:
-
Nein, Sonntags um 7.30 sind wir noch nicht in der Küche unterwegs ;-) - Aber wenn das wohlverdiente Ausschlafen schlagartig durch viel Sonne beendet wird hat sich der WAF ganz schnell erledigt
Einen Kommentar schreiben:
-
Du meinst, sie stand nackt in der Küche und wurde enttarnt
?
Einen Kommentar schreiben:
-
Moin,
bei mir läuft die automatische Beschattung zwar fast komplett über die Wetterstation und wurde angangs nur durch Parameter wie Temperatur (< 18°) bzw. Handeingabe gesperrt, aber ein Hinweis zum WAF:
umbedingt eine Zeit-Sperre berücksichtigen (z.B am Wochenende erst ab 9.30 aktiv) - Mein Küchenfeldwebel war an den ersten wärmeren Sommertagen "not amused" als Sonntags um 7.30 der Sonnenschutz die Rollläden auf 75% hochgefahren hat :-D (am Abend vorher händisch ausschalten und morgens wieder an, hat den WAF stark reduziert)
Grüße Lars
Einen Kommentar schreiben:
-
Hallo Jan!
Fände ich super, wenn du deine Arbeit hier posten könntest, zumindest auszugsweise. Damit bekommt man bestimmt gute Ideen
Ich habe seit neuem eine Wetterstation und stehe dann auch vor dem Problem der vernünftigen Lamellensteuerung..
Vielen Dank!
Einen Kommentar schreiben:

Einen Kommentar schreiben: