307 lines (259 with data), 20.8 kB
#!/usr/bin/perl
#
# Logikprozessor.pl - Konfiguration
#
$eibd_backend_address='0.0.0'; # eigene Adresse zur Vermeidung von Zirkellogiken, ist oft auch '1.1.254'
# Zentrale Einstellungen, insb. für die Prowl Mechanik, s. u..
%settings=(
prowl => {
apikey => "*** hier eigenen API-Key eintragen***",
application => 'WireGate KNX',
priority => 0,
event => '[unbenanntes Ereignis]',
description => '',
url => ''
},
samurai => "https://user:pass\@samurai.sipgate.net/RPC2",
);
%logic=(
# 1. Alle Werte, die auf einer GA gesendet werden, werden mit 2 multipliziert auf einer anderen GA weitergegeben
mal2 => { receive=>'9/5/201', transmit=>'9/5/202', translate => sub { 2*$input; }, },
# das "undef" steht da einfach, weil uns der letzte Ergebniswert nicht interessiert
# Namen von Logiken (hier "mal2" sind voellig frei, duerfen aber nicht mit einem "_" beginnen.
# 2. Die Werte auf der ersten GA werden aufsummiert, das Ergebis auf der anderen GA gesendet.
# Damit kann man bspw aus einem relativen Dimmwert einen absoluten Dimmwert machen.
sum => { receive=>'9/5/207', transmit=>'9/5/208', translate => sub { $state+$input; }, },
# $state enthaelt das jeweils letzte Ergebnis.
# 3. Wie oben, aber das Ergebnis limitiert auf den Bereich 0-100
lsum => { receive=>'1/2/7', transmit=>'1/2/8', translate => sub { limit(0,$state+$input,100); }, },
# 4. Memory-Funktion. Fuer KNX-Geraete, die kein Auslesen ihres Statuswerts zulassen (z.B. MDT DaliControl
# bei Einzel-EVG-Ansteuerung). Sehr einfach:
memory => { transmit=>'1/2/9', reply_to_read_requests=>1 },
# Hier geschieht folgendes: Wenn ein Write-Telegramm auf die Transmit-Adresse kommt,
# speichert der Logikprozessor den Wert immer automatisch ab. Eine Leseanfrage auf der transmit-GA hingegen wird
# immer mit dem letzten Wert (hier also dem gespeicherten) beantwortet. Eine receive-Adresse oder translate-Logik
# werden hier gar nicht gebraucht.
# 5. Hier eine "Treppenlichtfunktion". Auf jeden Schreibzugriff auf die receive-Adresse wird 10min spaeter eine 0 an
# die transmit-Adresse (hier gleich) geschickt.
stair => { receive=>'1/2/9', transmit=>'1/2/9', delay=>600, translate => 0, },
# Verzoegert wird uebrigens nur das Senden, nicht das Ausfuehren der translate-Routine.
# Neu ist hier der "delay"-Parameter, ausserdem der Spezialfall, dass translate einfach eine Konstante
# als Rueckgabewert spezifiziert.
# Weitere Bemerkungen:
# * translate darf nur entweder eine Konstante oder ausführbarer Code (sub {...}) sein.
# * Damit im Fall transmit==receive der Translator nicht auf sein eigenes Schreibtelegramm immer wieder antwortet,
# wird nur dann gesendet, wenn Ergebnis!=Input oder Sender des empfangenen Telegramms!=$eibd_backend_address (Wiregate).
# * Wenn waehrend der delay-Zeit erneut ein Receive-Telegramm empfangen wird, wird die Logik erneut ausgefuehrt und
# die delay-Zeit beginnt von vorne (wie bei einem Treppenlicht die Leuchtzeit bei erneutem Druecken verlaengert wird).
# * Schreibtelegramme auf die transmit-Adresse waehrend laufender delay-Zeit fuehren NICHT zum Speichern des Wertes
# wie in der Memory-Funktion! - sonst wuerde der spaeter (nach Ablauf des delays) gesendete Wert verfremdet.
# * Gibt eine Logik undef zurueck (return undef;), so geschieht NICHTS - auch der delay-Timer laeuft weiter mit dem
# bereits vorgemerkten Wert, der dann zu senden ist.
# * Gibt eine Logik den Text "cancel" zurueck (return "cancel";), so wird der delay-Timer geloescht. Auch hier bleibt
# der bereits vorgemerkte Wert weiter vorgemerkt - read-Requests auf die transmit-Adresse werden diesen Wert liefern.
# * Wird eine Logik waehrend der delay-Zeit erneut aufgerufen und liefert weder undef noch "cancel" zurueck, so
# wird der delay-Timer neu gesetzt und bei Ablauf der neue Wert gesendet. Der vorher vorgemerkte Wert wird dann also
# gar nicht gesendet.
# Als weitere Sicherheit vor Zirkellogiken (die ja auch im Zusammenwirken mehrerer Logiken entstehen koennen) gibt es
# die Klausel "cool=>...", die eine Logik nur dann zur Ausfuehrung zulaesst, wenn seit der letzten Ausfuehrung
# die spezifizierte Zahl von Sekunden verstrichen ist. Im obigen Beispiel also zB
stair2 => { receive=>'1/2/9', transmit=>'1/2/9', delay=>600, translate => 0, cool=>650 },
# So werden Zirkelschluesse komplett unmoeglich.
# Hinweise zu cool:
# * bei gesetztem "cool=>..." wird die Ausfuehrung der Logik auf receive-Telegramme (nicht aber die Reaktion auf
# Schreib/Lesetelegramme wie in Bsp 4) fuer die definierte Zeit verhindert.
# * Falls gleichzeitig "delay=>..." spezifiziert ist, so wird die erneute Ausfuehrung der Logik schon waehrend
# der delay-Zeit verhindert bis zum folgenden Schreibvorgang UND danach noch fuer die in cool=>... spezifizierte
# Anzahl Sekunden.
# * Ein Setzen von cool=>0 und delay=>10 hat den Effekt, dass die Ausfuehrung der Logik sofort nach Senden des
# transmit-Telegramms wieder erlaubt ist, aber NICHT waehrend der delay-Zeit (hier 10s).
# * gibt eine Logik den Wert "undef" zurueck, so hat dies generell KEINEN Effekt. Es wird dann nichts gesendet,
# und auch die cool-Zeit beginnt dann nicht zu laufen.
# 4b. Hier eine andere Loesung fuer KNX-geraete, die kein zyklisches Senden sondern nur Senden auf Aenderungen erlauben.
# Sendet ein KNX-Geraet auf 1/2/9 einen beliebigen Wert, so wird dieser alle 5min "bis in alle Ewigkeit" wiederholt -
# es sei denn, es kommt ein neuer Wert vom Geraet, dann wird ab sofort dieser neue Wert wiederholt.
# Eine solche Logik ist nuetzlich, um sicherzustellen, dass der jeweils aktuelle Wert immer im eibd-Cache vorhanden
# ist und somit von anderen Logiken genutzt werden kann.
repeater => { receive=>'1/2/9', transmit=>'1/2/9', delay=>'5min', transmit=>sub{$input} },
# Randbemerkung: fuer delay koennen auch Werte mit Einheiten wie '5min', '3h' oder '27s' eingesetzt werden
# 5. Hier eine Logik, die den Input bei Eintreffen mit 2 multipliziert, das Resultat aber nur speichert und erst
# spaeter auf ein explizites Lesetelegramm hin auf der transmit-Adresse sendet.
req => { receive=>'1/2/9', transmit=>'1/2/9', transmit_only_on_request=>1, translate => sub { 2*$input; }, },
# Uebrigens gibt es neben transmit_only_on_request auch transmit_changes_only (nur neue Werte uebertragen).
# Ausserdem gibt es noch transmit_on_startup (bei Neustart des Daemons/Logikprozessors) und transmit_on_config (bei
# Neukonfiguration des Logikprozessors)
# 6. Eine Logik, die einem Lichtanschalten gleich einen Dimmwert hinterherfeuert, und zwar tags und nachts einen verschiedenen:
dim => { receive=>'2/2/9', transmit=>'2/3/9', translate => sub { return unless $input; $day ? 80 : 3; } },
# Die Variablen $day_of_week (Mo...So), $calendar_week, $day_of_month (01-31), $month (01-12), $day_of_year (1-366),
# $year (2012), $date ('01/01'-'12/31'), $weekend (0 oder 1), $weekday (= nicht $weekend), $holiday (0 oder 1),
# $workingday (=nicht $weekend und nicht $holiday), $time_of_day ("08:34:02"),
# $hour ("08"), $day (1 falls zwischen 7 und 23 Uhr, 0 sonst) und $night (entsprechend umgekehrt)
# sind für diese Logiken vorbesetzt (bitte nicht darauf schreiben, koennte unverhergesehene Auswirkungen
# auf andere Logiken haben). $holiday beruecksichtigt dabei Neujahr, 1. Mai,
# 3. Oktober, Weihnachten/Ostern/Pfingsten, Christi Himmelfahrt und Fronleichnam als Feiertage.
# 8. Eine einfache UND-Logik mit zwei Eingaengen. Falls ein Telegramm auf einer der beiden receive-GAs empfangen wird,
# wird die andere Adresse noch ausgelesen, die Logik angewendet und das Ergebnis auf der transmit-GA uebermittelt
und => { receive=>['1/2/12','1/2/13'], transmit=>'1/2/14', translate => sub { $input->[0] && $input->[1]; }, debug=>1 },
# Das Flag debug=>1 bewirkt, dass diese (und nur diese!) Logik Meldungen ins Wiregate-Plugin-Log schreibt, die beim
# Debugging der Logik hilfreich sein koennten.
# 9. Ein komplexerer Fall nur zur Demonstration: hier besteht der Status des Logikprozessors aus mehreren Werten.
# Es wird der Mittelwert aus letztem, vorletztem und aktuellem Wert gesendet, und zwar mit 30s Verzoegerung.
mittelwert => { receive=>'9/5/205',
transmit=>'9/5/206',
delay=>30,
state => {val1=>0, val2=>0},
translate => sub { my $oldest=$state->{val2};
$state->{val2}=$state->{val1}; $state->{val1}=$input;
return ($oldest+$state->{val2}+$input)/3; },
},
# Wenn state ein Hash ist, wird der letzte gesendete Wert in $state->{result} gespeichert.
# 10. Eine Timer-Funktion. Hier eine Zeitschaltuhr, die immer am jeweils zweiten Dienstag jedes Monats
# um 08:00, um 10:00, zwischen 09:00 und 09:30 alle 2min und zwischen 18:00 und 20:00 jede volle Stunde
# eine 1 auf Transmit sendet
wecker => { transmit=>'10/1/15', timer=>{ time=>['08:00','10:00','09:00+2m-09:30','18:00+1h-20:00'],
day_of_month=>'8..14', day_of_week=>'Di' }, translate => 1 },
# Logiken mit timer-Klausel weichen in mehreren Punkten von den bisherigen Logiken ab:
# * sie ignorieren die cool-Klausel.
# * Die delay-Klausel wird nur dann angewendet, wenn die Logik aufgrund eines Telegramms auf einer receive-Adresse ausgefuert
# wird. Bei Aufrufen, die vom Timer ausgeloest wurden, wird delay ignoriert.
# * Wenn man uebrigens moechte, dass Telegramme auf receive-Adressen ignoriert werden (also reine Timerfunktion),
# so ersetze man 'receive' durch 'fetch', siehe auch unten
# * Die transmit_only_on_request-Klausel funktioniert wie gewohnt
# * Evtl. spezifizierte receive-Adressen werden beim Timer-Aufruf abgefragt, um das input-Array vorzubesetzen).
#
# Weitere Infos zu Timer-Spezifikationen
# * Als timer-Eintrag geht entweder ein einzelner Eintrag timer=>{...} wie oben oder eine Liste solcher Eintraege
# time=>[{...},{...},{...},...].
# * Jeder Eintrag MUSS eine Spezifikation time=>... (Varianten siehe Beispiel oben) enthalten.
# * Jeder Eintrag DARF zusaetzliche, die Geltungstage einschraenkende Klauseln wie year, month, day_of_year,
# day_of_month, calendar_week, day_of_week (Mo...So oder Mon...Sun oder 1...7) und date (Format 'MM/DD') enthalten.
# Solche Einträge duerfen Einzelwerte sein (year=>2012), Bereiche (day_of_month=>'8-14', date=>'02/12-03/15')
# oder auch wieder Listen von Werten/Bereichen.
# * Jeder Eintrag DARF zusaetzliche binaere (0 oder 1) Einschraenkungen enthalten: weekend, weekday,
# holiday und workingday (=weder holiday noch weekend). holiday beruecksichtigt dabei Neujahr, 1. Mai,
# 3. Oktober, Weihnachten/Ostern/Pfingsten, Christi Himmelfahrt und Fronleichnam als Feiertage.
# 11. Schlussendlich wieder mal Werbung fuer die GA-Kurznamen. Verwendet man GA-Namen mit eindeutigem Kuerzel
# (Kuerzel=erstes Wort des Namens), so kann man ab Wiregate PL32 auch diese Kuerzel verwenden.
# Mit meinem (Frys) Namensschema funktioniert bspw. das folgende:
D_SZ_Decke => { receive=>'LR_SZ_Decke_1', transmit=>'LK_SZ_Decke_1',
translate => sub { limit(0,$state+20*$input,100); }, },
# ist doch leserlicher, oder? Hier wird ein relativer Dimmwert (LR=Licht relativ, SZ=Schlafzimmer) durch Skalierung
# und Summierung in einen absoluten Wert umgewandelt
# 12. Prowl (http://www.prowlapp.com/) ist eine iPhone/iPad/i*-App zum Empfang von Growl-Kurznachrichten
# via Push. Da Prowl auch über eine Web-API Nachrichten entgegennehmen kann, ist eine Kopplung an eigene
# Anwendungen möglich. Für einen Minimalbetrieb ist Growl oder ein Mac nicht erforderlich: Es reicht die App auf dem
# Zielgerät und die Einrichtung von sog. API-Keys. Über prowl => ... lassen sich dann über den Logikprozessor
# Nachrichten per Push auf die App schicken.
# Zur Vereinfachung der einzelnen Aufrufe können in der Konfigurationsdatei des Logikprozessors zentral Standardwerte
# hinterlegt werden. Dazu ist neben den %logic Hash noch ein %settings Hash zu legen, der bestimmte Einstellungen
# unterstützt, siehe oben.
# Der Versand von Prowl-Nachrichten erfolgt nach der Verarbeitung von "translate", falls vorhanden. In der
# einfachsten Form erfolgt die Definition direkt mit dem gewünschten event-String (was die beschriebene Vergabe von
# Standardwerten voraussetzt):
simpleProwl => { receive => '1/2/3', prowl => 'Hello world!' }
# Wenn nun auf der GA 1/2/3 ein Wert eingeht wird 'Hello world!' auf die App gepusht. prowl => ... kann auch einen Hash
# entgegennehmen:
hashProwl => {
receive => '1/2/4',
prowl => {
application => 'Beschattung'
event => 'Südseite AB'
}
}
# Für komplexere Fälle kann auch eine sub übergeben werden, die dann einen passenden Hash zurückgeben muss. In die sub
# wird der Aufrufkontext übergeben, der den "input", das "result" und den "state" umfasst. Daneben stehen aber auch
# vereinfachte Skalare zur Verfügung, $input, $state und $result.
subProwl => {
receive => '1/2/5',
prowl => sub {
my (%context) = @_;
return (event => 'Rolladen Süd ' . ($input ? 'AB' : 'AUF'));
# oder eben: return (event => 'Rolladen Süd ' . ($context{input} ? 'AB' : 'AUF'));
}
}
# Falls nur unter bestimmten Bedingungen eine Prowl-Nachricht gesendet werden soll, kann sendProwl() jetzt auch
# aus der Logik-Subfunktion aufgerufen werden:
sendProwlOnWarning => {
receive => '1/2/3',
translate => sub {
if ($input)
{
# wird NUR gesendet, falls auf der 1/2/3 ein Wert ungleich 0 gesendet wird!
# bei 0 wird einfach gar nichts gesendet
sendProwl((
event => 'WARNUNG',
description => '1/2/3 wurde aktiviert!',
priority => 2
));
}
}
}
# Der Versand an mehrere Empfänger ist durch die Definition von "apikey" als Array möglich:
# apikey => [ "*** key 1 ***", "*** key 2 ***", ... ]
# 13. transmit_changes_only beschränkt eine Logik darauf, dass nur Aenderungen des Status auf den Bus geschickt werden.
# Dies kann bspw. genutzt werden, wenn zyklisch Temperaturwerte ausgewertet werden, aber nur "neue" Erkenntnisse auf
# den Bus kommen sollen.
logik_regenbrause_in_benutzung => {
receive => '4/1/71',
transmit => '6/1/0',
transmit_changes_only => 1,
translate => sub {
return ($input gt 25) ? 1 : 0;
}
}
# Weitere Optionen ohne Beispiel:
# 14. Die Optionen transmit_on_startup=>1 fuehrt die Logik bei der Initialisierung einmal aus.
# 15. Falls Read-Requests an die transmit-Adresse einer Logik geschickt werden, so wird das normalerweise ignoriert,
# es sei denn, reply_to_read_requests=>1 oder transmit_only_on_requests=>1 ist gesetzt (Uebertragung des letzten Resultats
# oder - falls keines vorhanden - erneute Berechnung) oder recalc_on_request=>1 ist gesetzt (erneute Ausfuehrung der Logik).
# Mit recalc_on_request=>0 wird die Ausfuehrung der Logik in solchen Faellen unterdrueckt.
# Bei mehreren transmit-Adressen fuer eine Logik wird grundsaetzlich NIEMALS auf read-requests geantwortet.
# 16. Die Option eibd_cache legt die Cache-Zeit bei knx_read-Aufrufen fest. Default ist 5min.
# 17a. Neben receive kann in fetch=>... eine weitere GA oder GA-Liste uebergeben werden. Diese GAs werden NICHT abonniert, und
# Telegramme hierauf lösen die Logik auch nicht aus, sie werden nur bei Ausfuehrung der Logik per knx_read ausgelesen und in
# $input hinterlegt
# 17b. Neben receive und fetch kann in trigger=>... eine weitere GA oder GA-Liste uebergeben werden. Diese GAs werden abonniert,
# Telegramme hierauf loesen die Logik aus, die Werte selbst werden aber NICHT abgefragt und auch NICHT in $input hinterlegt. Letzteres
# geschieht nur mit receive und fetch. In gewissen Sinne ist also "trigger=>'0/1/0', fetch=>'0/1/0'," aequivalent zu "receive=>'0/1/0',"
# letzteres ist nur effizienter. Ersteres (also trigger) ist aber viel effizienter, wenn man eine Logik durch viele GAs
# triggern moechte, OHNE dass dies weitere knx_read-Requests ausloest.
# 17c. Die Option trigger=>... hat noch eine zweite Syntax: man kann vorgeben, dass eine Sequenz von Telegrammen die Logik triggert.
# trigger=>['ga1==1', 'ga2>2', 'ga3==ANY', 'within 2min', 'all'} gibt vor, dass drei Telegramme innerhalb einer Minute empfangen
# werden muessen:
# Auf der GA ga1 muss der Wert 1 empfangen werden, auf ga2 ein Wert groesser 2, und ga3 muss kommen, wobei der Wert egal ist.
# Nur dann wird die Logik ausgefuehrt. Ein "==ANY" koennte auch entfallen, und statt 'all' koennte auch 'any' oder 'all_in_order'
# spezifiziert werden. Mit 'any' (das ist der Default) wird die Logik ausgefuehrt, sobald EINE der Bedingungen eingetreten ist.
# Mit 'all' muessen alle eintreten, und mit 'all_in_order' auch noch in der spezifizierten Reihenfolge.
# Ist 'within...' nicht angegeben, so gilt ein timeout von 60 Sekunden.
# 18. Es koennen auch mehrere GAs unter transmit=>[...] angegeben werden. Es wird allerdings an alle der gleiche Wert gesendet!
# 19. Mit der Option rrd=>'ABC', wird der an die transmit-Adresse gesendete Wert zusaetzlich in ein RRD geschrieben.
# Damit kann der Werteverlauf ueber die Zeit spaeter in eine Visualisierungsgrafik uebertragen werden.
# 20. Die Option execute_on_input_changes_only=>1 fuehrt die Logik nur aus, falls die vorliegenden Inputdaten sich
# gegenueber dem letzten Ausfuehren der Logik geaendert haben.
# 20b. Die Option execute_only_if_input_defined=>1 fuehrt die Logik nur aus, falls ALLE input- und fetch-Parameter definierte Werte haben
# 21. Ein Nachteil der timer-Option ist, dass sie nicht dynamisch (zB durch eine andere Logik) festgelegt werden kann.
# Einmal eingerichtet, bleibt der Timer unerbittlich. Eine zweite Luecke ist, dass manchmal Logiksequenzen in bestimmter
# zeitlicher Abfolge ausgefuehrt werden sollen. Das ist mit den oben beschriebenen Optionen nicht komfortabl moeglich.
# Theoretisch ist das mittels delay realisierbar, aber kann schnell unelegant und unuebersichtlich werden. Zur
# Schließung dieser Luecken wurde die Option followup eingefuehrt, die als Funktion followup() in translate-Funktionen
# verfuegbar ist. Diese definiert dynamisch Folgesequenzen anderer Logiken mit großen Freiheiten, was die zeitliche Definition angeht.
# Verwendung als Option
Tuer1_oeffnen => { transmit=>'Tuer1', translate=>1, followup=>{'Tuer2_oeffnen'=>2, 'Tuer3_oeffnen'=>3}, },
Tuer2_oeffnen => { transmit=>'Tuer1', translate=>1 },
Tuer3_oeffnen => { transmit=>'Tuer1', translate=>1 },
# Hier wird die zweite Tuer zwei Sekunden spaeter geoeffnet und die dritte drei Sekunden nach der ersten.
# Statt der 2 und 3 koennte da auch stehen '2s', oder '2min', oder '2h'.
# Auch moeglich ist es - in diesem Beispiel vielleicht wenig sinnvoll - eine Definition analog der Option timer zu hinterlegen:
Tuer1_oeffnen => { transmit=>'Tuer1', translate=>1, followup=>{'Tuer2_oeffnen'=>{time=>'08:00', weekday=>1}} },
# Hier wuerde die Tur bspw. geoeffnet, wenn das naechste Mal 8 Uhr an einem Wochentag ist.
# Alle Moeglichkeiten der Option timer stehen zur Verfuegung. Ein wichtiger Unterschied zu timer ist aber,
# dass mit dieser followup- Option nur eine EINMALIGE Ausfuehrung der Folgelogik verknuepft ist.
# Falls es transmit-Adressen gibt, wird der followup-Timer NUR DANN gesetzt, wenn davor auch etwas gesendet wurde.
# Konsequenterweise wird bei gleichzeitiger Verwendung von delay=>... und followup=>... der Followup-Timer erst mit der verzoegerten
# Sendung des Telegramms eingerichtet.
# Moechte man die evtl. laufenden followup-Timer loeschen, geht wieder ein einfaches return 'cancel', wie schon bei der Option delay.
# Verwendung von followup als Funktion: im Unterschied zur timer-Option laesst sich ein followup-Timer
# auch innerhalb einer translate-Funktion ausfuehren:
Tuer1_oeffnen => { transmit=>'Tuer1', translate=>sub { followup({'Tuer2_oeffnen'=>2, debug=>1}); }, },
# Dies geht ohne jedwede Einschraenkung. Zum Debuggen kann man dem followup-Hash noch eine Option debug=>1 mitgeben, wie oben gezeigt.
);