neues Modul für Ereignisgesteuertes Perl

Begonnen von Damian, 20 Februar 2018, 20:26:24

Vorheriges Thema - Nächstes Thema

Damian

Hallo,

ich sammle gerade Ideen für ein neues Modul. Es soll ereignisgesteuertes Perl für Automatisationsaufgaben werden.

Features:

- beliebige Perl-Blöcke
- Ereignis-Angaben in eckigen Klammern [:] triggern den jeweiligen Perl-Block, dieser wird ausgeführt. Diese Angaben stellen Funktionsaufrufe dar, sie können überall dort im Perl-Block angegeben werden, wo eine Perlfunktion vorkommen kann.
- Variablen außerhalb der Perlblöcke sind global innerhalb des Moduls und bleiben auch nach der Ausführung eines Blocks erhalten

Definitionsbereich DEF:

my $old_warm="";
my $mode="enable";
{# Ereignisgesteuerte Heizungssteuerung, dieser Block wird ausgeführt wenn das Reading "temperature" vom Device "sensor" ein Event erzeugt
  my $warm;
  return if ($mode eq "disable");                              # nichst tun wenn Modus disable
  $warm=([sensor:temperature] > 20 ? "on":"off");
  if ($warm != $old_warm) {                                   # wenn sich der Zustand warm/kalt ändert, dann soll die Heizung ein- bzw. ausgeschaltet werden
    if ($warm eq "off") {fhem"set heating on"} else {fhem"set heating on"};
    readingsSingleUpdate($hash, "state",$warm,1)              # der Status wird auf on gesetzt, wenn geheizt wird, sonst auf off
    $old_warm=$warm;                                           # der aktuelle Zustand wird in der globalen Variable $old_warm für den nächsten Durchlauf gespeichert
  }
}
{ # zeitgesteuert Heizung ein- und ausschalten, dieser Block wird um 05:00 und 09:00 Uhr ausgeführt
  if ([05:00]) {$mode="enable"} elsif ([09:00]) {$mode="disable";fhem"set heating off"}; #Modus wird zeitabhängig gesetzt
  readingsSingleUpdate ($hash, "mode",$mode,1);                                         # der aktuelle Modus wird im Reading mode gespeichert
}


In diesem Beispiel sind zwei Perlblöcke innerhalb eines Moduls programmiert, die unabhängig von einander ausgeführt werden.

Bei dem neuen Modul muss man sich mehr um Details kümmern, wie z. B. Status setzen, als beim DOIF-Modul, dafür ist man flexibler. Auf der anderen Seite muss man aber nicht gleich ein eigenes Modul programmieren, wenn man etwas systemnäher automatisieren will.
Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF

Ellert

Wäre es nicht besser, wenn die Variablen ausserhalb der Blöcke nur im Device global sind und nicht im Modul? Sonst müsste man für jedes Device neue Variablennamen verwenden.

Damian

#2
Zitat von: Ellert am 21 Februar 2018, 10:09:32
Wäre es nicht besser, wenn die Variablen ausserhalb der Blöcke nur im Device global sind und nicht im Modul? Sonst müsste man für jedes Device neue Variablennamen verwenden.

ja, so ist das auch gemeint. Die Variablen sollen pro Instanz global sein. Eigentlich will ich möglichst wenig Einfluss auf die Definition nehmen, damit sich Perl-Programmierer schnell zurechtfinden, daher wird es wohl besser sein, gleich mit hash-Variablen zu hantieren, statt sie intern zu maskieren - sie gelten pro Device-Instanz. Hier am Beispiel also:


$hash->{var}{old_warm}="";
$hash->{var}{mode}="enable";


Wenn eine Variable auch ein Reboot überleben soll, dann kann man sich mit Readings behelfen, z. B.:

$hash->{var}{mode}=[$SELF:mode,""];

oder auch

$hash->{var}{mode}=ReadingsVal($self,"mode","");

Damit wird die hash-Variable mode mit dem Reading mode der Device-Instanz nach dem Hochfahren des System belegt. Wenn das Reading nicht existiert. wird "" genommen. Die Option  Defaultwert-Angaben anzugeben liefert bereits die [:] - Syntax (entspricht der heutigen DOIF-Syntax).


Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF

JoWiemann

Hm, ich verstehe noch nicht den Mehrwert gegenüber at, notify und doif. Oder habe ich da gerade eine Scheuklappe?


Gesendet von iPhone mit Tapatalk

Grüße Jörg
Jörg Wiemann

Slave: RPi B+ mit 512 MB, COC (868 MHz), CUL V3 (433.92MHz SlowRF); FHEMduino, Aktuelles FHEM

Master: CubieTruck; Debian; Aktuelles FHEM

Damian

Zitat von: JoWiemann am 21 Februar 2018, 11:07:58
Hm, ich verstehe noch nicht den Mehrwert gegenüber at, notify und doif. Oder habe ich da gerade eine Scheuklappe?


Gesendet von iPhone mit Tapatalk

Grüße Jörg

Hast du schon mal versucht Ereignissteuerung kombiniert mit Zeitsteuerung mit übergreifenden Variablen gekapselt als Objekt zu programmieren? Wenn ja, dann hast du ein FHEM-Modul programmiert. at, notify aber auch DOIF kann das nicht sauber abbilden.

Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF

Phill

Klingt irgendwie interessant, aber mir will kein wirkliches konkretes Problem dafür einfallen, welches dadurch einfacher gelöst wird? Was war denn der Auslöser für die Idee?

Das Beispiel aus dem ersten Beitrag ist ja, wenn ich mich nicht irre, mit 2 DOIF Zeilen lösbar! Oder?

Mir fehlt irgendwie noch ein Zeiger auf die Klammerblöcke, damit man diese zusätzlich aus anderen Blöcken ansprechen/triggern könnte.
Homebrew 1-Wire / HomeMatic Mix - Cubietruck mit FHEM als Server - Raspberry PI 3 als Informationsanzeige im MagicMirror Stil - Raspberry Pi 1 als Klingelanlage - VDR

Mein Modul: Talk2Fhem - Mein Tipp: https://forum.fhem.de/index.php/topic,82442.0.html

Damian

#6
Zitat von: Phill am 21 Februar 2018, 14:25:53
Klingt irgendwie interessant, aber mir will kein wirkliches konkretes Problem dafür einfallen, welches dadurch einfacher gelöst wird? Was war denn der Auslöser für die Idee?

Das Beispiel aus dem ersten Beitrag ist ja, wenn ich mich nicht irre, mit 2 DOIF Zeilen lösbar! Oder?

Mir fehlt irgendwie noch ein Zeiger auf die Klammerblöcke, damit man diese zusätzlich aus anderen Blöcken ansprechen/triggern könnte.

Natürlich wäre das Beispiel mit 2 DOIFs, ja vermutlich sogar auch mit einem lösbar.

Wahrscheinlich sind die meisten einfachen Lampe an/aus - Problemstellungen kürzer und eleganter mit DOIF lösbar, immerhin wird Zeit und Ereignissteuerung innerhalb eines Moduls unterstützt.

Das Problem entsteht, wenn die Anforderungen steigen. Dann kann man versuchen alles in ein DOIF zu packen oder auf mehrere zu verteilen.

Im ersten Fall wird es irgendwann unübersichtlich, da DOIF eine flache Hierarchie benutzt - es dreht sich alles um den Zustand von State.
Im anderen Falle wird ein Problem auf mehrere DOIF-Module funktional verteilt. Wenn man eine Strukturierung vornehmen will, dann kann man die zusammen hängenden DOIF-Module in einen Raum packen, vielleicht auch eine gemeinsame Gruppe definieren, das war es aber auch schon.

Wenn man aber den Gedanken der Kapselung im Sinne von Objektorientierung verfolgt, dann müsste man schon ein FHEM-Modul programmieren.
Der Aufwand dürfte aber für die meisten Anforderung einfach zu hoch sein.

Irgendwo dazwischen sollte das neue Modul seinen Einsatz finden können.

Aufgrund, der vielen Funktionen, die ich für DOIF programmiert habe, könnte ich mir vorstellen relativ schnell einen Prototypen zu bauen - die meisten Funktionen dafür stecken schon in einem DOIF. Danach kann man damit etwas rumspielen und schauen, wie gut/elegant oder schwierig Probleme damit lösbar sind.

Ich denke, viele Anfänger wird es abschrecken, da es Perl ist. Auf der anderen Seite finde ich es reizvoll, Ereignis- und Zeisteuerung in Perl unterzubringen, ich meine damit bewusst nicht: Perl in einem Ereignis- oder Zeit-Modul unterzubringen - das haben wir ja schon ;)
Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF

Damian

#7
so könnte ein generalisierter Perl-Block aussehen. Dieser Code beinhaltet sowohl die Thermostatsteuerung als auch die zeitabhängige Vorgabe der Temperaturen für alle Zimmer.

{# Ereignisgesteuerte Thermostatsteuerung, dieser Block wird ausgeführt, wenn das Reading "temperature" von irgendeinem Device ein Event erzeugt
  my $temp=[":temperature",0];
  return if (ReadingsVal($self,"mode","disable") eq "disable");                           
  my $warm=($temp > ReadingsVal($device,"desired",20) ? "on":"off");
  if (!defined $hash->{var}{$device}{warm} or $warm != $hash->{var}{$device}{warm}) {       
    if ($warm eq "off") {
       fhem"set switch_$device on"
    } else {
       fhem"set switch_$device off"
    }
    $hash->{var}{$device}{warm}=$warm;                                 
  }
}

{# in diesem Block werden die Vorgabetemperaturen zeitabhängig gesetzt
  if ([05:00] or [14:00]) {
    fhem"setreading Kueche desired 20";
    fhem"setreading Bad desired 21";
    fhem"setreading Wohnzimmer desired 19";
    fhem"setreading KinderZimmer1 desired 20";
  } elsif ([09:00] or [22:00]) {
    fhem"setreading Kueche desired 19";
    fhem"setreading Bad desired 20";
    fhem"setreading Wohnzimmer desired 18";
    fhem"setreading KinderZimmer1 desired 19";
  }
}

Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF

Ellert

Wie soll das Modul heissen? PerlMutt, PerlHabour, DOPE(rl) ;)

Damian

#9
Zitat von: Ellert am 22 Februar 2018, 16:22:47
Wie soll das Modul heissen? PerlMutt, PerlHabour, DOPE(rl) ;)

Ganz einfach: DOIF

Und das Beste - es läuft schon :)

Der Perl-Modus wird automatisch erkannt.

Das Modul ist voll abwärtskompatibel. Es ist gerade mal 1 % größer.

Definitionen außerhalb der Perlblöcke sind noch nicht eingebaut.

meine ersten Tests sehen so aus:

defmod di_test2 DOIF {if ([FS] eq "on") {fhem"set bla1 on"}}\
{if ([FS] eq "off") {fhem"set bla2 on"}}\
{if ([19:23]) {fhem"set bla3 on"}}\
{if ([19:23]) {fhem"set bla4 on"}}\
{if ([19:24]) {fhem"set bla5 on"}}


Edit: Anhang entfernt
Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF

Ellert

Ich hatte gedacht, es wird ein um die 10kB schlankes Modul, es müssten eigentlich "nur" die Trigger registriert werden. :)

Damian

#11
Zitat von: Ellert am 22 Februar 2018, 23:26:17
Ich hatte gedacht, es wird ein um die 10kB schlankes Modul, es müssten eigentlich "nur" die Trigger registriert werden. :)

ja, uiTable sollte nicht fehlen, da ein Status nicht mehr ausreicht, DOIF_Readings sind wichtig, um zyklische Sensoren sauber abzufangen, Timer-Verwaltung, usw. -> 1 % + 80 % (DOIF-Funktionalität) ;)
Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF

Per

DIE Killeranwendung dafür habe ich noch nicht gefunden, aber wahrscheinlich wird sich die Benutzung "schleichend" ergeben. Wie die Perlblöcke in {}, die ja jetzt schon gehen (gleibe ich ;)).

Damian

#13
Zitat von: Per am 23 Februar 2018, 11:42:29
DIE Killeranwendung dafür habe ich noch nicht gefunden, aber wahrscheinlich wird sich die Benutzung "schleichend" ergeben. Wie die Perlblöcke in {}, die ja jetzt schon gehen (gleibe ich ;)).

An dem einfachen Beispiel sieht man schon etwas, was in einem DOIF so nicht funktionieren würde.

Die Blöcke sind völlig unabhängig von einander. Beim Event von FS kann also Block 1 und Block 2 etwas ausführen. Genauso bei der Zeitangabe, um 19:23 wird sowohl Block 3 und Block 4 ausgeführt. Das Beispiel ist hier sinnfrei, da es nur einen Test darstellt.

Man kann festhalten: DOIF: ein Event -> nur ein Zweig kann ausgeführt werden, DOIF(Perl): ein Event -> beliebig viele Zweige können ausgeführt werden.

DOIF(Perl) kennt keine Zustände, es gilt also immer do always. Weil es einfach gestrickt ist, gibt es keine internen Abhängigkeiten, die man erklären müsste. Es funktioniert eher so, wie ein Programmierer es erwartet hätte, dafür muss man sich selbst drum kümmern Zustände zu verwalten und Readings/Status selbst zu setzen.

Auf der anderen Seite kann man bei DOIF(Perl) auf der Perlebene mit Variablen arbeiten, mit hash->{var} hat man die Möglichkeit pro Instanz beliebig viele Variablen zu nutzen, die nach der Ausführung erhalten bleiben und beim nächsten Event ausgewertet werden können. Beim DOIF muss man sich da mit Readings auf der FHEM-Ebene behelfen. Ein einfaches hochzählen dürfte Faktor 1000 langsamer sein.
Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF

Damian

#14
So, was noch fehlt ist ein Ersatz für watchdog, sequenz, waittimer.

Beispiel

{ if ([FS:"on"]) {
    set_DOIF_timer(300,"Timer 1");
  } elsif ([FS:"off"]) {
    del_DOIF_timer("Timer 1");
  } elsif ([$SELF:"Timer 1"]) {
    fhem "set lamp on";
  }
}


mit
set_DOIF_timer (<sekunden>, <Timername>, <Trigger optional>)
del_DOIF_timer(<Timername>);

Damit würde man beim Event "on" von FS einen Timer setzen mit dem Namen "Timer 1", wenn zwischendurch ein "off" von FS kommt wird der Timer gelöscht, wenn der Block allerdings durch einen Timer-Trigger geweckt wird, dann wird Lampe eingeschaltet.

Da man beim set_DOIF_timer Events erzeugen kann z. B. set_DOIF_timer (300,"mytimer",1), so kann man systemweit darauf reagieren.

Edit: Syntax überarbeitet
Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF