Ein device als Parameter an eine subroutine übergeben

Begonnen von McShire, 05 Februar 2021, 00:59:34

Vorheriges Thema - Nächstes Thema

McShire

Hallo zusammen,
ich möchte gerne einen wiederkehrenden Algorithmus in einer subroutine in einer Datei 99_myUtils...pm ausführen lassen. Dabei muss ich innerhalb der sub auch devices bzw. dummies auslesen oder ändern.

Als Parameter können in Perl nur Scalare übergeben werden.
Nun möchte ich eine Reference auf ein device übergeben und darüber Zugriff auf das device im sub haben und im aufrufenden Block den geänderten Wert bekomme.

Ich habe schon viel versucht und gelesen und ausprobiert, aber leider bisher ohne Erfolg. Hat jemand eine Idee wie ich z.B.
aus einem notify über $NAME das auslösende Gerät als Parameter oder
ein dummy device, dessen Wert in der sub geändert und zurückgegeben wird,
an die sub übergeben und zurückgeben kann?
Viel Grüße
Werner

pwlr

#1
Moin,

ein als Beispiel passendes notify hab ich grad nicht, aber hier ne Version mit einem userReading:
pct:(trigger_cnt.*) {userReadings_02($name)}

und dann in einer sub in der 99_myUtils...pm:
sub userReadings_02($) {
# translate state into reading pct (open, closed, tilted) - used for sensors
# open   -> 100
# tilted ->  50
# closed ->   0

my($name) = @_;
my$sub_name = "userReadings_02";
Log3 $name, 4, "$name $sub_name: start";

my$pct=ReadingsVal($name,"state","300");
my$returnValue = $pct;

if ($pct eq "closed") {
$returnValue = 0;
};
if ($pct eq "tilted") {
$returnValue = 50;
};
if ($pct eq "open") {
$returnValue = 100;
};
Log3 $name, 4, "$name $sub_name: end with returnvalue $returnValue";
return $returnValue;
};


Im userReading ist $name der device-name des aufrufenden Devices, den Du in der sub mit
my($name) = @_;
bekommst. Und dann kannst Du Dich "weiterhangeln" und z.B. Redings des Devices lesen.
Der Returncode aus der sub landet dann im reading pct.

Es können auch mehrere Paramerter überben werden, die werden dann in einer durch Komma getrennten Liste analog zu dem Beispiel übergeben.
sub userReadings_02($$) {
my($name, $parameter) = @_;


Mit verbose im Device kannst Du die Debug-Ausgabe Log3 $name, 4, "$name ..."
steuern.

EDIT : Keine eigenen Readings des Devices auf diese Art überschreiben / ändern. Mein System verabschiedet sich dann in einen Loop and bye..

Alles klar ?
Moin
Bernd

CoolTux

Du kannst in Perl nicht nur Scalare bei Funktionsaufrufen der Funktion über geben. Es gehen auch Hash oder Array. Sogar Referenzen kann man über geben und auch zurück geben.
Du musst nicht wissen wie es geht! Du musst nur wissen wo es steht, wie es geht.
Support me to buy new test hardware for development: https://www.paypal.com/paypalme/MOldenburg
My FHEM Git: https://git.cooltux.net/FHEM/
Das TuxNet Wiki:
https://www.cooltux.net

Christoph Morrison

Zitat von: pwlr am 05 Februar 2021, 01:59:22

my$sub_name = "userReadings_02";


Den Namen der aktuellen Subroutine bekommst du übrigens über
(caller(0))[3], z.B.


use Data::Dumper;
use feature 'say';

sub eine_sub {
    say Dumper((caller(0))[3]);
}

eine_sub();
# $VAR1 = 'main::eine_sub';



McShire

Danke für die Infos. Ich werde es damit probieren. Es wird sicherlich klappen.
Falls nicht, frage ich noch einmal nach.
Soweit ich das in den Tutorials gelesen und verstanden habe, muss man bei allen
Parametern, die nicht scalare sind, als Parameter die Reference auf das jeweilige Element
übergeben.
Viele Grüße
Werner

pwlr

Moin,

ZitatDen Namen der aktuellen Subroutine bekommst du übrigens über
Code: [Auswählen]

(caller(0))[3]

danke für die Info, guter Tipp  !!!
Bernd

McShire

Vielen Dank für die Anregungen. Ich werde das jetzt über globale Variable lösen.
Ein Beispiel für den Aufruf und die Auswertung:


{  #test fuer subroutine
         
           
            if (ReadingsVal('SD_GT_E0E65_B', 'state', 'undef') eq 'on')  {
                 
                 FK_open_do('closed',15.0,$NAME);;

            }
 
            elsif (ReadingsVal('SD_GT_E0E65_B', 'state', 'undef') eq 'off') {
               
                FK_open_do('open',5.0,$NAME);;

            }

            fhem "set Hobby_EA $main::old_FK_Hobby";;
            fhem "set Hobby_Test $main::old_HZ_Hobby";;
            fhem "set FK_name $main::name";;
      }


Die Sub in myUtils sieht dann so aus:


##############################################
# $Id: myUtilsTemplate.pm 21509 2020-03-25 11:20:51Z rudolfkoenig $
#
# Save this file as 99_myUtils.pm, and create your own functions in the new
# file. They are then available in every Perl expression.

package main;

use strict;
use warnings;

sub
myUtils_WS_Initialize($$)
{
  my ($hash) = @_;

# globale Variable zur Steuerung der Alarmanlage und Heizung durch die Fensterkontakte
# Hierin werden jeweils die alten Werte vor einer Aenderung gespeichert

  our $old_FK_Hobby = 'closed';
  our $old_HZ_Hobby = 5.0;
  our $name = 'xxx'

}

# Enter you functions below _this_ line.

#########################################################

# FK_open_do:
# fuehrt Reaktionen auf das Oeffen eines Fensters aus:
# der Name des jeweiligen Fensterkontakts ist Bestandteil des auslösenden Ereignisses
# und steht in $NAME. Für den MAX!-Thermostat in dem Raum gilt:
# Die sub setzt bei Öffnen des Fensters bei Anwesenheit die Solltemperatur für die
# Heizung (sofern vorhanden) auf 5 Grad, speichert die vorherige Temperatur
# und setzt den Sollwert im Thermostat beim Schliessen des Fensters wieder
# auf den alten Wert. Dabei wird der mode auto während der Öffnungszeit auf man gesetzt.
#
# Bei Abwesenheit wird bei Öffnen des Fensters der Alarm ausgelöst und per
# Anruf auf dem Mobiltelefon und als Email gemeldet.
#
# Parameter: Name des Fensterkontaktes, i.a. in $NAME
#            Alarmzustand: "on", "off"
#            Anwesenheit:state "anwesend", "abwesend"

sub FK_open_do {

# parameter in local Variable
  my $Hobby_test = $_[0];
  my $Hobby_AE   = $_[1];
  my $name       = $_[2];

# parameter in globla Variable, auch im aufrufendem Programm verfügbar
  $main::old_FK_Hobby  = $_[0];
  $main::old_HZ_Hobby  = $_[1];
  $main::name          = $_[2];

}


1;


Für Verbesserungen bin ich immer zu haben
Viele Grüße
Werner

CoolTux

Sorry aber verstehe da nicht wirklich was Du da machen willst. Für mich sieht es alles andere als der richtige Weg aus.
Wenn ich das richtig verstehe übergibst Du Deiner Funktion doch nur einen Skalar mit dem Wert aus $NAME. Und dann liest Du in der Funktion die Übergabe in mehrere Skalare weil Du denkst es ist ein Array? Ich verstehe das ganze Konstrukt nicht. Tut mir leid. Und das mit den globalen Variablen ist für mich auch mehr wie Fragwürdig. Das sollte man lieber lassen.
Du musst nicht wissen wie es geht! Du musst nur wissen wo es steht, wie es geht.
Support me to buy new test hardware for development: https://www.paypal.com/paypalme/MOldenburg
My FHEM Git: https://git.cooltux.net/FHEM/
Das TuxNet Wiki:
https://www.cooltux.net

McShire

Dieses script ist nicht das Programm was dort entstehen soll sondern nur ein Beispiel,
dass so die Daten aus dem notify heraus an die sub übergeben werden können, $Name ist das auslösende device, brauche ich, weil ich in der sub für unterschiedliche Geräte immer den gleichen Algorithmus anwende. Die Auslösung im notify ist nachher .*open und in der sub werden dann das auslösende Gerät und die zugehörigen anderen devices behandelt.
Die Variablen, die ich an die sub übergebe, werden in der sub behandelt und als globale Variable werden dem Aufrufprogramm die veränderten Werte zur Verfügung gestellt.
Man hätte die Parameter auch als Reference übergeben können, dann werden die Änderungen auch im Aufrufprogramm verfügbar. Aber dass hat mit Dummys irgendwie nicht geklappt.
Viele Grüße
Werner

CoolTux

Was spricht gegen


...
my ($old_FK_Hobby,$old_HZ_Hobby,$name) = FK_open_do('open',5.0,$NAME);
...
fhem "set Hobby_EA $old_FK_Hobby";
fhem "set Hobby_Test $old_HZ_Hobby";
fhem "set FK_name $name";
...



Sub

sub FK_open_do {

# parameter in local Variable
  my $Hobby_test = $_[0];
  my $Hobby_AE   = $_[1];
  my $name       = $_[2];

  return ($Hobby_test,$Hobby_AE,$name);
}


1;
Du musst nicht wissen wie es geht! Du musst nur wissen wo es steht, wie es geht.
Support me to buy new test hardware for development: https://www.paypal.com/paypalme/MOldenburg
My FHEM Git: https://git.cooltux.net/FHEM/
Das TuxNet Wiki:
https://www.cooltux.net

Beta-User

Also für mich sieht die ganze Konstruktion auch suboptimal aus, und irgendwie ist auch das "Parameter in lokale Variable" "eigenwillig"....

Zitat von: McShire am 06 Februar 2021, 11:09:16
Man hätte die Parameter auch als Reference übergeben können, dann werden die Änderungen auch im Aufrufprogramm verfügbar. Aber dass hat mit Dummys irgendwie nicht geklappt.
Wieso Dummy?
Wieso irgendwo in globalen Variablen?

Falls du selbstgebastelte Abhängigkeiten irgendwo hinterlegen willst, sind mAn. entweder das Reading associatedWith (das ist aber eher als Generalverweis brauchbar) oder userAttr das "Mittel der Wahl".

Bei mir ist z.B. folgende "Kette" eingebaut:
Tür- oder Fensterkontakt wird open/closed/tilted => (ein globales) notify wird ausgelöst und ruft eine sub auf. Übergeben wird
{ myWinContactNotify ($NAME, $EVENT, 30)}Der Code schaut dann über Attribute, welche Thermostate dazugehören (eigentlich: welche mit denen gepeerten virtuellen Kontakte) und setzt die dann (bei Öffnen zeitverzögert => letztes Argument, wie lange gewartet werden soll) auf offen oder zu. Dabei wird zur "Sendezeit" nochmal geschaut, ob noch irgendeiner der zugehörigen Tür/FK's überhaupt offen ist (das ganze auch nur, wenn  grade Heizperiode ist...).

Geht alles über Attributabfrage und ist über FHEMWEB direkt einsehbar, kein "Rumgepfusche" mit Variablennamen, die ggf. irgendjemand anderes grade aus welchen Gründen auch immer auch in main  haben will...

(Ansonsten gäbe es auch noch %data, das ist für User-Infos vorgesehen...)
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: ZigBee2mqtt, MiLight@ESP-GW, BT@OpenMQTTGw | ZWave | SIGNALduino | MapleCUN | RHASSPY
svn: u.a Weekday-&RandomTimer, Twilight,  div. attrTemplate-files, MySensors

McShire

Danke für die Infos,
als Anfänger kennt man halt noch nicht alle Möglichkeiten und
findet auch nicht alles in den tutorials.
Ich werde jetzt mal mit Euren Hinweisen eine mehr professionelle Lösung versuchen.

Letztlich soll es bei mir genau wie oben beschrieben, eine Kette werden. ein Fenster oder eine Tür werden geöffnet -> .*open Ereignis
triggert ein notify, -> Name des auslösers, sowie die aktuelle Situation (abwesend (Alarm ja, nein -> SID, Debianmail), Designated Temp, usw.)
werden an eine sub übergeben, in der Sub die entsprechenden Maßnahmen für die zum Auslöser (Fensterkontakt) relevanten Geräte und routinen,
-> Status und Eistellungen zuück ans FHEM
mit den jetzt erhaltenen Infos wird es wohl klappen.

Der Umstieg vom Algol und Cobol der 60er und 70er auf moderne Sprachen ist halt nicht an einem Tag zu machen.
viele Grüße
Werner

Beta-User

Vielleicht dann mal hier der virtuelle Fensterkontakt mit den userAttr:
defmod Virtueller_Tuerkontakt_WZ CUL_HM 33221101
attr Virtueller_Tuerkontakt_WZ userattr myRealFK myTimeout myRealTempsensor
attr Virtueller_Tuerkontakt_WZ devStateIcon set_offen:fts_door_slide_open@red:geschlossen set_geschlossen:fts_door_slide@green:offen
attr Virtueller_Tuerkontakt_WZ eventMap /postEvent open:offen/postEvent closed:geschlossen/
attr Virtueller_Tuerkontakt_WZ group Türen und Fenster
attr Virtueller_Tuerkontakt_WZ icon fts_door_right_open
attr Virtueller_Tuerkontakt_WZ model VIRTUAL
attr Virtueller_Tuerkontakt_WZ myRealFK Balkontuer,Terrassentuer_WZ,Fenster_Wohnzimmer_SSO,Fenster_Wohnzimmer_SSW
attr Virtueller_Tuerkontakt_WZ peerIDs 2E7A9B03,2E832C03
attr Virtueller_Tuerkontakt_WZ room Steuerung->Heizung
attr Virtueller_Tuerkontakt_WZ webCmd :

Der myUtils-Code dazu (könnte man auch noch verschönern, aber geht...):
sub myWinContactNotify {  #three parameters, last (timeout) is optional
  my ($window, $event, $timeout) = @_;
  $timeout = 90 if !$timeout;
  my @virtuals = devspec2array("TYPE=CUL_HM:FILTER=model=VIRTUAL:FILTER=myRealFK=.*$window.*");
  for my $virtual (@virtuals) {
    my $myreals = AttrVal($virtual,"myRealFK","");
    if ($event =~ /open|tilted/) {
      my $checktime = gettimeofday()+$timeout;
      InternalTimer($checktime,"myTimeoutWinContact",$virtual);   
    } else {
      my @wcs = split(',',$myreals);
      my $openwc = 0;
      for my $wc (@wcs) {
        $openwc++ if (ReadingsVal($wc,"state","closed") ne "closed");
        last if $openwc;
      }
      CommandSet (undef,"$virtual geschlossen") if !$openwc;
    }
  }
  return;
}

sub myTimeoutWinContact {
  my $name = shift // return;
  #my $name = $hash{NAME};
  return if !ReadingsVal("Heizperiode","state","off") eq "on";
  my $myreals = AttrVal($name,"myRealFK","");
  my @wcs = split(',',$myreals);
  my $openwc = 0;
  for my $wc (@wcs) {
    $openwc++ if ReadingsVal($wc,"state","closed") ne "closed";
    last if $openwc;
  }
  CommandSet (undef,"$name offen") if $openwc;
  return;
}
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: ZigBee2mqtt, MiLight@ESP-GW, BT@OpenMQTTGw | ZWave | SIGNALduino | MapleCUN | RHASSPY
svn: u.a Weekday-&RandomTimer, Twilight,  div. attrTemplate-files, MySensors

McShire

Danke für den Tip und das Beispiel.
Ich habe zwar einige Zeit gebraucht, um den virtuellen Kontakt und das Handling der userattr zu verstehen sowie die Funktion der sub.
Offenbar geht es um mehrere FK, die alle die gleiche Aktion (Heizung Wohnzimmer ein / nach Zeit wieder aus ) ausführen, dabei gibt es offenbar mehrere virtuelle FK, jeweils für die realen FK in einem Zimmer oder Bereich:
Lange habe ich gebraucht das define einzugeben und es zum Laufen zu bringen. Die Fehlermeldung ist immer: Erst das Device anlegen:
Bis mir dann die Eingabe kam, das HM-devices 6-stellige Definition haben und darum nur 332211 aber nicht 33221101 geht.

Bei mir ist es etwas einfacher, ich habe für jedes Zimmer nur FK, der beim Öffnen die Heizung herunter regelt und beim Schließen den alten Zustand (gespeichert in userattr (Temp, Mode) wiederherstellt und FKs wo es keine Heizung gibt userattr HZ_Steu 0).
Je nach Status anwesend / abwesend und oder Alarm an/aus wird für einige Bereiche oder das ganze Haus die Alarmanlage gesteuert.
Langer rede kurzer Sinn:
Die Speicherung der Werte in Userattr haben den Durchbruch gebracht, dass hätte ich ohne das Beispiel nicht verstanden. Da ich nur jeweils einen realen FK habe können bei mir die virtuelleN FK für diesen Zweck wegfallen und die Routine wird ziemlich einfach. Auch die Funktionen AttrVal und OldreadingsVal kannt eich vorher nicht. devspec2arry sowieso nicht. Viel gelernt.
Vielen Dank und bleib gesund.
Werner


Beta-User

Sorry, dass ich das an einem etwas "schwierigen" Device erläutern musste, hatte halt grade nichts besseres...
Diese HM-Kanal-Devices haben schon 8 Stellen, es gibt aber noch (jeweils) ein "Mutterdevice" mit 6 Stellen dazu, aber letztlich ist das ja für die Funktionalität nicht sooo wichtig.

Wie du richtig erkannt hast, gibt es mehrere "Echt-" Kontakte, die zu einem virtuellen zusammengefasst werden, der dann seinerseits wieder für einen oder mehrere Thermostate "zuständig" ist (gelöst über direktes peering, das regelt dann automatisch runter bzw. wieder zurück auf den zuvor eingestellten Wert (bzw. das, was das Wochenprogramm ggf. sagt)).

Falls ich aktuelle Werte zwischenspeichern müßte, würde ich das aber nicht über Attribute machen, sondern über Readings! Siehe "setreading" und "ReadingsVal()" bzw. "ReadingsNum()" in den Perl-Specials der commandref (bzw. deletereading). Auch da kann man sich passende Namen einfallen lassen, ich beginne mittlerweile alles, was "von mir" ist (und nicht über Module automatisiert kommt) in der Regel mit "my.*". Ansonsten ist es praktsich dasselbe...

Meine Handhabung ist die:
Alles, was statisch ist, soll in Attribute, die Konfiguration will ich möglichst wenig anfassen.
Alles, was sich im laufenden Betrieb ändert, kommt möglichst in Readings.

Das klappt im Großen und Ganzen, es gibt allerdings manche Module, die da teilweise eine etwas andere Handhabung "aus alter Zeit" haben und auch z.B. Werte, die nach diesem Verständnis als Reading geführt werden sollten in ein Attribut schreiben; ist aber die Ausnahme und meistens auch unveränderlich.

Was das mit Readings als "Marker" angeht: Z.B. hatte ich früher (vor der Umstellung auf AutoShuttersControl) zur Steuerung meiner Tag/Nacht-Fahrten meiner Rollläden einen WeekdayTimer, der einfach über einen FILTER immer die Rollläden auf- und zugemacht hat, die  zum Ausführungszeitpunkt eben einen bestimmten Readingwert gesetzt hatten. Auch zu FILTER findest du einige wenige markante Beispiele in der commandref.

Viel Spaß noch beim Weitertüfteln!
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: ZigBee2mqtt, MiLight@ESP-GW, BT@OpenMQTTGw | ZWave | SIGNALduino | MapleCUN | RHASSPY
svn: u.a Weekday-&RandomTimer, Twilight,  div. attrTemplate-files, MySensors