Einstieg in die Modul-Entwicklung

Begonnen von vocaris, 07 November 2017, 23:55:21

Vorheriges Thema - Nächstes Thema

vocaris

Hallo Community,

ich habe nach dieser Anleitung http://www.juergenstechnikwelt.de/smarthome-2/smarthome-mit-fhem-umsetzung-meines-abfallkalenders/ eingerichtet.

Alles sieht auch super aus - allerdings gefällt mir nicht, dass man an so vielen Stellen "rumfummeln" muss. Also hatte ich die Idee mir ein kleines Modul zu schreiben, mit welchem man die Resttage bis zu einem Eintrag in einem Kalender berechnen kann (ein solches Modul könnte ich auch noch ein einigen anderen Stellen verwenden). Ich habe mir also die Seite https://wiki.fhem.de/wiki/DevelopmentModuleIntro größtenteils durchgelesen und mit meinem Modul angefangen. Dabei habe ich natürlich auch große Teile der Anleitung zum Abfallkalender übernommen und werde mein Modul, wenn es fertig ist natürlich auch dem Autor des Abfallkalenders und der Community zur Verfügung stellen.

Da hier natürlich mehr als ein Wert aus dem Kalender gelesen werden müsste, muss das berücksichtigt werden.
Den Aufruf habe ich so vorgesehen:

define abfallResttage CalenderDaysTill abfallCalendar
attr abfallResttage searches .*Papier.* .*Wertstoff.*
attr abfallResttage reading-names GrueneTonne GelbeTonne


Raus kommen sollen in diesem Beispiel zwei Readings:

GrueneTonne = 3
GelbeTonne = 5


Hier mein Code (Die CommandRef hab ich noch nciht gepflegt):

package main;
use strict;
use warnings;

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

    $hash->{DefFn}      = 'CalendarDaysTill_Define';
$hash->{NotifyFn} = 'CalendarDaysTill_Notify';
$hash->{AttrFn}     = 'CalendarDaysTill_Attr';

  $hash->{AttrList} =
          "searches: "
. "reading-names: "
        . $readingFnAttributes;
}

sub CalendarDaysTill_Define($$) {
    my ($hash, $def) = @_;
    my ($name, $type, $calendar) = split('[ \t]+', $def, 3);


return "wrong define syntax: you must specify a device name for using FB_CALLLIST" if(!defined($calendar));
    return "wrong define syntax: define <name> CalendarDaysTill <calendar>" if(!$calendar);
 
if($init_done)
    {
        return "define error: the selected device $calendar does not exist." unless(defined($defs{$calendar}));

        Log3 $name, 3, "CalendarDaysTill ($name) - WARNING - selected device $calendar ist not of type Calendar" unless($defs{$calendar}->{TYPE} eq "Calendar");
    }
   
$hash->{CALENDAR} = $calendar;
   
notifyRegexpChanged($hash, $calendar . ":triggered");

CalendarDaysTill_RefreshReadings($hash);

    return undef;   
}

sub CalendarDaysTill_Notify($$){
my ($own_hash, $dev_hash) = @_;
my $ownName = $own_hash->{NAME}; # own name / hash

return "" if(IsDisabled($ownName)); # Return without any further action if the module is disabled

my $devName = $dev_hash->{NAME}; # Device that created the events

my $events = deviceEvents($dev_hash,1);
return if( !$events );

foreach my $event (@{$events}) {
$event = "" if(!defined($event));

if ($event eq "state: triggered"){
CalendarDaysTill_RefreshReadings($own_hash);
}
# Examples:
# $event = "readingname: value"
# or
# $event = "INITIALIZED" (for $devName equal "global")
#
# processing $event with further code
}
}

sub CalendarDaysTill_Attr(@) {
my ($cmd,$name,$attr_name,$attr_value) = @_;
my $hash = $defs{$name};

if($cmd eq "set") {
        if($attr_name eq "searches") {
return "Invalid argument: values must be set" if (!defined($attr_value));

CalendarDaysTill_RefreshReadings($hash);
} elsif($attr_name eq "reading-names") {
return "Invalid argument: values must be set" if (!defined($attr_value));

CalendarDaysTill_RefreshReadings($hash);
} else {
    return "Unknown attr $attr_name";
}
}
return undef;
}

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

my $name = $hash->{NAME};
my $searches = AttrVal($name, "searches", undef);
my $readingNames = AttrVal($name, "reading-names", undef);
my $calendar = $hash->{CALENDAR};

return undef if (!defined($searches) or !defined($readingNames));

Log3 $name, 1, "CalendarDaysTill: searches" . $searches;
Log3 $name, 1, "CalendarDaysTill: readings" . $readingNames;
   
my $t  = time;
my @readingNamesArray = split('[ \t]+', $readingNames);
my @searchesArray = split('[ \t]+', $searches);
my $uid;
my $dayDiff;
 
readingsBeginUpdate($hash);

for(my $i=0; $i<4; $i++)
{
$dayDiff = -1;

my @uids = split(/;/,fhem("get $calendar find $searchesArray[$i]"));
       
# den nächsten Termine finden
foreach $uid (@uids)
{
my $eventDate = CalendarDaysTill_CalendarDate($calendar, $uid);
my $dayDiffNeu = floor(($eventDate - $t) / 60 / 60 / 24 + 1);
if ($dayDiffNeu >= 0 && ($dayDiffNeu < $dayDiff || $dayDiff == -1))
{
$dayDiff = $dayDiffNeu;
}
}
       
readingsBulkUpdate($hash, $readingNamesArray[$i], $dayDiff);
   }

readingsEndUpdate($hash, 1);
}

#
# Hilfsfunktion für Kalenderauswertungen
#

sub CalendarDaysTill_CalendarDate($$)
{
   my ($KalenderName, $KalenderUid) = @_;
   my $dt = fhem("get $KalenderName start uid=$KalenderUid 1");
   my $ret = time - (2*86400);  #falls kein Datum ermittelt wird Rückgabewert auf "vorgestern" -> also vergangener Termin;

   if ($dt and $dt ne "")
   {
      my @SplitDt = split(/ /,$dt);
      my @SplitDate = split(/\./,$SplitDt[0]);
      $ret = timelocal(0,0,0,$SplitDate[0],$SplitDate[1]-1,$SplitDate[2]);
   }

   return $ret;
}

1;


Das Modul funktioniert auch soweit. Allerdings habe ich nun ein paar Fragen:
1. Wieso kann ich den Raum nicht für mein Modul setzen (attr <name> room <raum>)?
2. Ich würde gerne die beiden Attribute charmanter setzen, schließlich könnte es vorkommen, dass ein Regulärer Ausdruck auch mal Leerschritte beinhaltet. Wie kann ich hier direkt ein Array ( also { ".*Papier.*", ".*Wertstoff.*" } ) angeben und dieses auch im Code als Array verwenden?
3. In den Methoden CalendarDaysTill_RefreshReadings und CalendarDaysTill_CalendarDate verwende ich die Methode fhem. Ist das der Ideale Weg oder gibt es einen anderen, mit dem den Kalender besser ansprechen kann?
4. Was müsste für eine minimal-Implementierung noch rein?
5. Wo habe ich gegen Best-Practices verstoßen?
6. Klappt die Kalenderaktualisierung so wie ich diese eingebaut habe? Ich habe leider nur eine statische ICal-Datei hinterlegen können...
7. Einmal am tag müsste ich meine Readings aktualisieren, damit der Tag runterzählt. Wie realisiere ich das?

Vielen Dank schon mal
Manfred

mumpitzstuff


Wzut

Zitat von: vocaris am 07 November 2017, 23:55:21
1. Wieso kann ich den Raum nicht für mein Modul setzen (attr <name> room <raum>)?
3. In den Methoden CalendarDaysTill_RefreshReadings und CalendarDaysTill_CalendarDate verwende ich die Methode fhem. Ist das der Ideale Weg oder gibt es einen anderen, mit dem den Kalender besser ansprechen kann?
7. Einmal am tag müsste ich meine Readings aktualisieren, damit der Tag runterzählt. Wie realisiere ich das?

1. ????
3. Wenn ich in meinen Modulen ein set, get usw. benötige verwende ich i.d.R. CommandSet / CommandGet direkt aus der fhem.pl, schau dir die Subs dort mal an.
7. Schreib doch bei 95_holiday.pm ab , dort wird ein Timer aufgezogen der zwei Sekunden nach Mitternacht abläuft und die neuen Berechnungen macht -> sub holiday_refresh
Maintainer der Module: MAX, MPD, UbiquitiMP, UbiquitiOut, SIP, BEOK, readingsWatcher

vocaris

@mumpitzstuff
Natürlich habe ich das Modul gesehen... Es kann auch gut sein, dass ich auf dieses Modul umsteige, aber es ging mir ja gerade darum einen Einstieg in die PERL- / FHEM-Programmierung zu finden. Und zu diesem Zweck schien es mir eine gute Idee zu sein, ein fast fertiges Script in einem Modul umzusetzen...

@Wzut
1. Wenn ich den Befehl "attr calendarDaysWaste room Wetter" eingebe, erhalte ich die Rückmeldung "Unknown attr room". In anderen Modulen habe ich aber auch nciht gesehen, dass man die Standard attrs selber ausprogrammieren muss und bin daher davon ausgegangen, dass diese automatisch verarbeitet werden...
2. & 3. Danke für den Tipp, das werde ich mir heute abend nach Feierabend mal ansehen.

CoolTux

#4

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

    $hash->{DefFn}       = 'CalendarDaysTill_Define';
    $hash->{NotifyFn} = 'CalendarDaysTill_Notify';
    $hash->{AttrFn}        = 'CalendarDaysTill_Attr';

    $hash->{AttrList} = "searches ".
                      "reading-names ".
                                      $readingFnAttributes;
  }


room group und so weiter kommen durch die $readingFnAttributes hinzu.
Die doppel Punkte am ende der Attributsnamen brauchst Du nicht, nur wenn Du danach weitere Auswahlmöglichkeiten schaffen willst.
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

vocaris

Zunächst mal vielen Dank für die Antworten...
Also in meiner Mittagspause hatte ich nun doch etwas Zeit:

@Wzut
2. Super Danke, sieht irgendwie stimmiger aus.
3. Hab ich jetzt mal implementiert, mal schauen, ob diese Nacht eine Aktualisierung läuft...

@CoolTux
Danke für den Hinweis mit den Doppelpunkten. $readingFnAttributes habe ich doch angegeben, wieso versteht mein Modul das Attribut room nicht?
Ich habe mal den Inhalt von $readingFnAttributes ins Log ausgegeben, dort ist room auch nicht drin...

Hier mal die Ausgabe im Log:
event-on-change-reading event-on-update-reading event-aggregator event-min-interval stateFormat:textField-long timestamp-on-change-reading


marvin78

#6
room ist ein Standard Attribut (kommt aus fhem.pl $AttrList) und nicht Teil von $readingFnAttributes.

vocaris

Ok, aber warum versteht mein Modul das Attribut dann nicht?

marvin78

Findest du es in der Attributliste der Detailansicht deines Modul-Devices?

Wzut

schau dir mal deine sub _Attr an , du verbietest es selbst :)
und btw :
return "wrong define syntax: you must specify a device name for using FB_CALLLIST" if(!defined($calendar));
Warum eine Callist .... ok damit man sieht wo du abgeschrieben hast ?
Maintainer der Module: MAX, MPD, UbiquitiMP, UbiquitiOut, SIP, BEOK, readingsWatcher

marvin78

Hehe. So weit runter hatte ich gar nicht gescrollt.

vocaris

Ahhhh....

Ich hatte gedacht, die Standards werden vor Ausführung meiner _Attr-Methode ausgeführt... Hatte das so aus dem Beispiel-Modul übernommen... Jetzt klappts auch mit dem Raum...

PS: Natürlich schreib ich ab, das sind schließlich meine ersten Schritte in Perl...

Wzut

Die sub _Attr kannst du IMHO in deinem Fall eh ganz löschen.
Und was das Thema abschreiben angeht so verfahre ich noch heute nach dem alten Motto "Good programmers write good code; great programmers borrow code" oder halt auch "The amateur imitates, the genius steals."
Maintainer der Module: MAX, MPD, UbiquitiMP, UbiquitiOut, SIP, BEOK, readingsWatcher

marvin78

Ich weiß ja nicht, was er mit seinem Modul vor hat. So wie er schreibt, ist es ein Modul für seinen Privatgebrauch.


Ich sehe das aber ohnehin etwas anders: Wenn man den Code aus anderen Modulen nutzt, um daraus zu lernen und ihn dann ggf. auch in abgewandelter Form wiederverwendet (wenn dein Modul eine andere Funktion haben soll, bringt es ja nichts, einfach nur zu kopieren, in der Regel muss man den Code verstehen und dann abwandeln), ist nicht dagegen einzuwenden. Ich würde nicht einmal den Teil unterschreiben, dass man nur ein guter Programmierer ist, wenn man jeden einzelnen Buchstaben selbst schreibt. Im Gegenteil.


Klauen ist ohnehin etwas anderes als borgen bzw. wiederverwenden. Wenn ein Modul dann tatsächlich hier veröffentlicht wird, sollte man den Autor, von dem man geborgt hat ggf. fragen, ob man das so machen kann. Das gehört zur guten Erziehung. Das Rad neu zu erfinden, muss jedoch nicht immer sein. Gerade nicht, wenn man versucht zu lernen.

Wzut

fragen oder einfach mit in die Quellenangabe aufnehmen

grep geklaut /opt/fhem/FHEM/*.pm
Maintainer der Module: MAX, MPD, UbiquitiMP, UbiquitiOut, SIP, BEOK, readingsWatcher