FHEM Forum

FHEM => Anfängerfragen => Thema gestartet von: vocaris am 07 November 2017, 23:55:21

Titel: Einstieg in die Modul-Entwicklung
Beitrag von: vocaris am 07 November 2017, 23:55:21
Hallo Community,

ich habe nach dieser Anleitung http://www.juergenstechnikwelt.de/smarthome-2/smarthome-mit-fhem-umsetzung-meines-abfallkalenders/ (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 (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
Titel: Antw:Einstieg in die Modul-Entwicklung
Beitrag von: mumpitzstuff am 08 November 2017, 01:21:55
https://wiki.fhem.de/wiki/ABFALL (https://wiki.fhem.de/wiki/ABFALL)

Das hier kennst du?
Titel: Antw:Einstieg in die Modul-Entwicklung
Beitrag von: Wzut am 08 November 2017, 07:38:05
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
Titel: Antw:Einstieg in die Modul-Entwicklung
Beitrag von: vocaris am 08 November 2017, 09:26:36
@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.
Titel: Antw:Einstieg in die Modul-Entwicklung
Beitrag von: CoolTux am 08 November 2017, 09:46:18

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.
Titel: Antw:Einstieg in die Modul-Entwicklung
Beitrag von: vocaris am 08 November 2017, 13:49:48
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

Titel: Antw:Einstieg in die Modul-Entwicklung
Beitrag von: marvin78 am 08 November 2017, 14:03:27
room ist ein Standard Attribut (kommt aus fhem.pl $AttrList) und nicht Teil von $readingFnAttributes.
Titel: Antw:Einstieg in die Modul-Entwicklung
Beitrag von: vocaris am 08 November 2017, 14:09:20
Ok, aber warum versteht mein Modul das Attribut dann nicht?
Titel: Antw:Einstieg in die Modul-Entwicklung
Beitrag von: marvin78 am 08 November 2017, 14:12:40
Findest du es in der Attributliste der Detailansicht deines Modul-Devices?
Titel: Antw:Einstieg in die Modul-Entwicklung
Beitrag von: Wzut am 08 November 2017, 14:13:37
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 ?
Titel: Antw:Einstieg in die Modul-Entwicklung
Beitrag von: marvin78 am 08 November 2017, 14:16:41
Hehe. So weit runter hatte ich gar nicht gescrollt.
Titel: Antw:Einstieg in die Modul-Entwicklung
Beitrag von: vocaris am 08 November 2017, 14:21:33
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...
Titel: Antw:Einstieg in die Modul-Entwicklung
Beitrag von: Wzut am 08 November 2017, 14:50:01
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."
Titel: Antw:Einstieg in die Modul-Entwicklung
Beitrag von: marvin78 am 08 November 2017, 14:58:25
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.
Titel: Antw:Einstieg in die Modul-Entwicklung
Beitrag von: Wzut am 08 November 2017, 15:23:34
fragen oder einfach mit in die Quellenangabe aufnehmen

grep geklaut /opt/fhem/FHEM/*.pm
Titel: Antw:Einstieg in die Modul-Entwicklung
Beitrag von: vocaris am 08 November 2017, 20:08:41
Da ich bereits seit ~30 Jahren programmiere, denke ich, ich weiß, wie man den Autor eines Codings würdigt. Und natürlich habe ich nicht nur planlos rberkopiert, sondern auch verstanden wie er den Code umgesetzt hat... ;)

Aber Perl gehörte nie zu meinen Lieblingssprachen - um genau zu sein, habe ich immer einen großen Bogen um Perl gemacht... Ich komme eigentlich aus der C#-Ecke. In letzter Zeit mache ich sehr viel JavaScript als Frontend im Webdesign, aber auch mit NodeJS...