diff --git a/fhem/CHANGED b/fhem/CHANGED index 05fc8fb42..6f84a9631 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - change: 57_SSCal: integrate SMUtils lib, minor fixes - bugfix: 02_FTUISRV: fix empty loop includes results - feature: 31_HUEDevice: added gesture reading for Aqara Cube added readingList attribute diff --git a/fhem/FHEM/57_SSCal.pm b/fhem/FHEM/57_SSCal.pm index 53e003217..772c36ca4 100644 --- a/fhem/FHEM/57_SSCal.pm +++ b/fhem/FHEM/57_SSCal.pm @@ -36,6 +36,37 @@ use warnings; eval "use JSON;1;" or my $SSCalMM = "JSON"; ## no critic 'eval' # Debian: apt-get install libjson-perl use Data::Dumper; # Perl Core module use GPUtils qw(GP_Import GP_Export); # wird für den Import der FHEM Funktionen aus der fhem.pl benötigt + +use FHEM::SynoModules::API qw(apistatic); # API Modul + +use FHEM::SynoModules::SMUtils qw( + completeAPI + showAPIinfo + showModuleInfo + addSendqueueEntry + listSendqueue + purgeSendqueue + checkSendRetry + startFunctionDelayed + evaljson + getClHash + delClHash + delReadings + setCredentials + getCredentials + setReadingErrorState + setReadingErrorNone + login + logout + moduleVersion + updQueueLength + trim + jboolmap + ); # Hilfsroutinen Modul + +use FHEM::SynoModules::ErrCodes qw(expErrors); # Error Code Modul + + use MIME::Base64; use POSIX qw(strftime); use Time::HiRes qw(gettimeofday); @@ -111,6 +142,9 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "2.4.2" => "03.10.2020 get from SMUtils: completeAPI showAPIinfo evaljson setReadingErrorState setReadingErrorNone showModuleInfo ". + "login logout getClHash delClHash trim moduleVersion updQueueLength delReadings checkSendRetry startFunctionDelayed ". + "minor fix in periodicCall ", "2.4.1" => "20.05.2020 new function 'evalTimeAndWrite' ", "2.4.0" => "19.05.2020 more changes according to PBP, switch to packages, fix cannot delete (and display in table) EventId of block 0 ", "2.3.0" => "25.04.2020 set compatibility to Calendar package 2.3.4-0631, some changes according to PBP ", @@ -159,6 +193,18 @@ my %vNotesIntern = ( "1.0.0" => "18.12.2019 initial " ); +my %hset = ( # Hash für Set-Funktion (needcred => 1: Funktion benötigt gesetzte Credentials) + credentials => { fn => \&_setcredentials, needcred => 0 }, + calUpdate => { fn => \&_setcalUpdate, needcred => 1 }, + deleteEventId => { fn => \&_setdeleteEventId, needcred => 1 }, + eraseReadings => { fn => \&_seteraseReadings, needcred => 0 }, + listSendqueue => { fn => \&listSendqueue, needcred => 0 }, + logout => { fn => \&_setlogout, needcred => 0 }, + purgeSendqueue => { fn => \&purgeSendqueue, needcred => 0 }, + restartSendqueue => { fn => \&_setrestartSendqueue, needcred => 1 }, + cleanCompleteTasks => { fn => \&_setcleanCompleteTasks, needcred => 1 }, +); + # Versions History extern my %vNotesExtern = ( "2.4.0" => "19.05.2020 Switched to Perl packages. Use FHEM::SSCal::calAsHtml from now instead of ". @@ -179,59 +225,9 @@ my %vHintsExt_de = ( "gemäß RFC5545. " ); -# Aufbau Errorcode-Hashes -my %errauthlist = ( - 400 => "No such account or the password is incorrect", - 401 => "Account disabled", - 402 => "Permission denied", - 403 => "2-step verification code required", - 404 => "Failed to authenticate 2-step verification code", -); - -my %errlist = ( - 100 => "Unknown error", - 101 => "No parameter of API, method or version", - 102 => "The requested API does not exist - may be the Synology Calendar package is stopped", - 103 => "The requested method does not exist", - 104 => "The requested version does not support the functionality", - 105 => "The logged in session does not have permission", - 106 => "Session timeout", - 107 => "Session interrupted by duplicate login", - 114 => "Missing required parameters", - 117 => "Unknown internal error", - 119 => "session id not valid", - 120 => "Invalid parameter", - 160 => "Insufficient application privilege", - 400 => "Invalid parameter of file operation", - 401 => "Unknown error of file operation", - 402 => "System is too busy", - 403 => "The user does not have permission to execute this operation", - 404 => "The group does not have permission to execute this operation", - 405 => "The user/group does not have permission to execute this operation", - 406 => "Cannot obtain user/group information from the account server", - 407 => "Operation not permitted", - 408 => "No such file or directory", - 409 => "File system not supported", - 410 => "Failed to connect internet-based file system (ex: CIFS)", - 411 => "Read-only file system", - 412 => "Filename too long in the non-encrypted file system", - 413 => "Filename too long in the encrypted file system", - 414 => "File already exists", - 415 => "Disk quota exceeded", - 416 => "No space left on device", - 417 => "Input/output error", - 418 => "Illegal name or path", - 419 => "Illegal file name", - 420 => "Illegal file name on FAT file system", - 421 => "Device or resource busy", - 599 => "No such task of the file operation", - 800 => "malformed or unsupported URL", - 805 => "empty API data received - may be the Synology cal Server package is stopped", - 806 => "couldn't get Synology cal API information", - 810 => "The credentials couldn't be retrieved", - 900 => "malformed JSON string received from Synology Calendar Server", - 910 => "Wrong timestamp definition. Check attributes \"cutOlderDays\", \"cutLaterDays\". ", -); +# Standardvariablen +my $splitstr = "!_ESC_!"; # Split-String zur Übergabe in getCredentials, login & Co. +my $queueStartFn = "FHEM::SSCal::getApiSites"; # Startfunktion zur Queue-Abarbeitung ################################################################ sub Initialize { @@ -301,47 +297,40 @@ sub Define { my $model = "Diary"; $model = "Tasks" if( grep {$_ eq "Tasks"} @a ); - $hash->{ADDR} = $addr; - $hash->{PORT} = $port; + $hash->{SERVERADDR} = $addr; + $hash->{SERVERPORT} = $port; $hash->{MODEL} = "Calendar"; - $hash->{PROT} = $prot; + $hash->{PROTOCOL} = $prot; $hash->{MODEL} = $model; $hash->{RESEND} = "next planned SendQueue start: immediately by next entry"; - $hash->{HELPER}{MODMETAABSENT} = 1 if($modMetaAbsent); # Modul Meta.pm nicht vorhanden - $hash->{HELPER}{CALFETCHED} = 0; # vorhandene Kalender sind noch nicht abgerufen - $hash->{HELPER}{APIPARSET} = 0; # es sind keine API Informationen gesetzt -> neu abrufen + $hash->{HELPER}{MODMETAABSENT} = 1 if($modMetaAbsent); # Modul Meta.pm nicht vorhanden + $hash->{HELPER}{CALFETCHED} = 0; # vorhandene Kalender sind noch nicht abgerufen - CommandAttr(undef,"$name room SSCal"); - CommandAttr(undef,"$name event-on-update-reading .*Summary,state"); - - $data{SSCal}{$name}{calapi} = { # in Define um unterschiedliche DSM mit div. Versionen zu ermöglichen - "APIINFO" => { "NAME" => "SYNO.API.Info" }, # Info-Seite für alle API's, einzige statische Seite ! - "APIAUTH" => { "NAME" => "SYNO.API.Auth" }, # API used to perform session login and logout - "CALCAL" => { "NAME" => "SYNO.Cal.Cal" }, # API to manipulate calendar - "CALEVENT" => { "NAME" => "SYNO.Cal.Event" }, # Provide methods to manipulate events in the specific calendar - "CALSHARE" => { "NAME" => "SYNO.Cal.Sharing" }, # Get/set sharing setting of calendar - "CALTODO" => { "NAME" => "SYNO.Cal.Todo" }, # Provide methods to manipulate events in the specific calendar - }; + CommandAttr(undef, "$name room SSCal"); + CommandAttr(undef, "$name event-on-update-reading .*Summary,state"); - # Versionsinformationen setzen - setVersionInfo($hash); + my $params = { + hash => $hash, + notes => \%vNotesIntern, + useAPI => 1, + useSMUtils => 1, + useErrCodes => 1 + }; + use version 0.77; our $VERSION = moduleVersion ($params); # Versionsinformationen setzen - # Credentials lesen - getcredentials($hash,1,"credentials"); + getCredentials($hash,1,"credentials",$splitstr); # Credentials lesen - # Index der Sendequeue initialisieren - $data{SSCal}{$name}{sendqueue}{index} = 0; + $data{SSCal}{$name}{sendqueue}{index} = 0; # Index der Sendequeue initialisieren readingsBeginUpdate ($hash); readingsBulkUpdateIfChanged ($hash, "Errorcode" , "none"); readingsBulkUpdateIfChanged ($hash, "Error" , "none"); - readingsBulkUpdateIfChanged ($hash, "QueueLength", 0); # Länge Sendqueue initialisieren - readingsBulkUpdate ($hash, "nextUpdate" , "Manual"); # Abrufmode initial auf "Manual" setzen - readingsBulkUpdate ($hash, "state" , "Initialized"); # Init state + readingsBulkUpdateIfChanged ($hash, "QueueLength", 0); # Länge Sendqueue initialisieren + readingsBulkUpdate ($hash, "nextUpdate" , "undefined"); # Abrufmode initialisieren + readingsBulkUpdate ($hash, "state" , "Initialized"); # Init state readingsEndUpdate ($hash,1); - # initiale Routinen nach Start ausführen , verzögerter zufälliger Start - initOnBoot($name); + initOnBoot($name); # initiale Routinen nach Start ausführen , verzögerter zufälliger Start return; } @@ -362,6 +351,7 @@ sub Undef { BlockingKill($hash->{HELPER}{RUNNING_PID}) if($hash->{HELPER}{RUNNING_PID}); delete $data{SSCal}{$name}; + RemoveInternalTimer($name); return; @@ -379,7 +369,7 @@ sub DelayedShutdown { my $name = $hash->{NAME}; if($hash->{HELPER}{SID}) { - logout($hash); # Session alter User beenden falls vorhanden + logout($hash, $data{SSCal}{$name}{calapi}, $splitstr); # Session alter User beenden falls vorhanden return 1; } @@ -401,14 +391,13 @@ sub Delete { my $name = $hash->{NAME}; my $index = $hash->{TYPE}."_".$hash->{NAME}."_credentials"; - # gespeicherte Credentials löschen - setKeyValue($index, undef); + setKeyValue($index, undef); # gespeicherte Credentials löschen return; } ################################################################ -sub Attr { ## no critic 'complexity' +sub Attr { ## no critic 'complexity' my ($cmd,$name,$aName,$aVal) = @_; my $hash = $defs{$name}; my $model = $hash->{MODEL}; @@ -442,12 +431,14 @@ sub Attr { ## no critic 'comple if($@) { Log3($name, 2, "$name - Error while evaluate: ".$@); return $@; - } else { + } + else { $attrVal = $av if(ref($av) eq "HASH"); } } $hash->{HELPER}{$aName} = $attrVal; - } else { + } + else { delete $hash->{HELPER}{$aName}; } @@ -461,7 +452,8 @@ sub Attr { ## no critic 'comple if ($do == 1) { RemoveInternalTimer($name); - } else { + } + else { InternalTimer(gettimeofday()+2, "FHEM::SSCal::initOnBoot", $name, 0) if($init_done); } @@ -476,25 +468,24 @@ sub Attr { ## no critic 'comple } if($aName =~ m/interval/x) { RemoveInternalTimer($name,"FHEM::SSCal::periodicCall"); - if($aVal > 0) { - InternalTimer(gettimeofday()+1.0, "FHEM::SSCal::periodicCall", $name, 0); - } + InternalTimer (gettimeofday()+1.0, "FHEM::SSCal::periodicCall", $name, 0); } } return; } -################################################################ -sub Set { ## no critic 'complexity' +############################################################################################# +# Setter +############################################################################################# +sub Set { my ($hash, @a) = @_; return "\"set X\" needs at least an argument" if ( @a < 2 ); my $name = $a[0]; my $opt = $a[1]; my $prop = $a[2]; my $prop1 = $a[3]; - my $prop2 = $a[4]; - my $prop3 = $a[5]; + my $model = $hash->{MODEL}; my ($success,$setlist); @@ -516,233 +507,331 @@ sub Set { ## no critic 'compl $evids = join(",", @unique); } - if(!$hash->{CREDENTIALS}) { - # initiale setlist für neue Devices - $setlist = "Unknown argument $opt, choose one of ". - "credentials " - ; - } elsif ($model eq "Diary") { # Model Terminkalender - $setlist = "Unknown argument $opt, choose one of ". - "calUpdate ". - "credentials ". - ($evids?"deleteEventId:$evids ":"deleteEventId:noArg "). - "eraseReadings:noArg ". - "listSendqueue:noArg ". - "logout:noArg ". - ($idxlist?"purgeSendqueue:-all-,-permError-,$idxlist ":"purgeSendqueue:-all-,-permError- "). - "restartSendqueue:noArg " - ; - } else { # Model Aufgabenliste - $setlist = "Unknown argument $opt, choose one of ". - "calUpdate ". - "cleanCompleteTasks:noArg ". - "credentials ". - ($evids?"deleteEventId:$evids ":"deleteEventId:noArg "). - "eraseReadings:noArg ". - "listSendqueue:noArg ". - "logout:noArg ". - ($idxlist?"purgeSendqueue:-all-,-permError-,$idxlist ":"purgeSendqueue:-all-,-permError- "). - "restartSendqueue:noArg " - ; - } - - if ($opt eq "credentials") { ## no critic 'Cascading' - return "The command \"$opt\" needs an argument." if (!$prop); - logout($hash) if($hash->{HELPER}{SID}); # Session alter User beenden falls vorhanden - ($success) = setcredentials($hash,$prop,$prop1); - - if($success) { - addQueue($name,"listcal","CALCAL","list","&is_todo=true&is_evt=true"); - getApiSites($name); - return "credentials saved successfully"; - } else { - return "Error while saving credentials - see logfile for details"; - } - - } elsif ($opt eq "listSendqueue") { - my $sub = sub { - my ($idx) = @_; - my $ret; - for my $key (reverse sort keys %{$data{SSCal}{$name}{sendqueue}{entries}{$idx}}) { - $ret .= ", " if($ret); - $ret .= $key."=>".$data{SSCal}{$name}{sendqueue}{entries}{$idx}{$key}; - } - return $ret; - }; - - if (!keys %{$data{SSCal}{$name}{sendqueue}{entries}}) { - return "SendQueue is empty."; - } - my $sq; - for my $idx (sort{$a<=>$b} keys %{$data{SSCal}{$name}{sendqueue}{entries}}) { - $sq .= $idx." => ".$sub->($idx)."\n"; - } - return $sq; + $setlist = "Unknown argument $opt, choose one of "; - } elsif ($opt eq "purgeSendqueue") { - if($prop eq "-all-") { - delete $hash->{OPIDX}; - delete $data{SSCal}{$name}{sendqueue}{entries}; - $data{SSCal}{$name}{sendqueue}{index} = 0; - return "All entries of SendQueue are deleted"; - } elsif($prop eq "-permError-") { - for my $idx (keys %{$data{SSCal}{$name}{sendqueue}{entries}}) { - delete $data{SSCal}{$name}{sendqueue}{entries}{$idx} - if($data{SSCal}{$name}{sendqueue}{entries}{$idx}{forbidSend}); - } - return "All entries with state \"permanent send error\" are deleted"; - } else { - delete $data{SSCal}{$name}{sendqueue}{entries}{$prop}; - return "SendQueue entry with index \"$prop\" deleted"; - } + if(!$hash->{CREDENTIALS}) { # initiale setlist für neue Devices + $setlist .= "credentials "; + } - } elsif ($opt eq "calUpdate") { # Termine einer Cal_id (Liste) in Zeitgrenzen abrufen - return "Obtain the Calendar list first with \"get $name getCalendars\" command." if(!$hash->{HELPER}{CALFETCHED}); - - my $cals = AttrVal($name,"usedCalendars", ""); - shift @a; shift @a; - my $c = join(" ", @a); - $cals = $c ? $c : $cals; - return "Please set attribute \"usedCalendars\" or specify the Calendar(s) you want read in \"$opt\" command." if(!$cals); - - # Kalender aufsplitten und zu jedem die ID ermitteln - my @ca = split(",", $cals); - my ($oids,$caltype,@cas); - if($model eq "Diary") { $caltype = "Event"; } else { $caltype = "ToDo"; } - for my $cal (@ca) { - my $oid = $hash->{HELPER}{CALENDARS}{"$cal"}{id}; - next if(!$oid); - if ($hash->{HELPER}{CALENDARS}{"$cal"}{type} ne $caltype) { - Log3($name, 3, "$name - The Calendar \"$cal\" is not of type \"$caltype\" and will be ignored."); - next; - } - $oids .= "," if($oids); - $oids .= '"'.$oid.'"'; - push (@cas, $cal); - Log3($name, 2, "$name - WARNING - The Calendar \"$cal\" seems to be unknown because its ID couldn't be found.") if(!$oid); - } - return "No Calendar of type \"$caltype\" was selected or its ID(s) couldn't be found." if(!$oids); - - Log3($name, 5, "$name - Calendar selection for add queue: ".join(',', @cas)); - - if($model eq "Diary") { # Modell Terminkalender - my ($err,$tstart,$tend) = timeEdge ($name); - if($err) { - Log3($name, 2, "$name - ERROR in timestamp: $err"); - - my $errorcode = "910"; - - readingsBeginUpdate ($hash); - readingsBulkUpdateIfChanged ($hash, "Error", $err); - readingsBulkUpdateIfChanged ($hash, "Errorcode", $errorcode); - readingsBulkUpdate ($hash, "state", "Error"); - readingsEndUpdate ($hash,1); - - return "ERROR in timestamp: $err"; - } - - my $lr = AttrVal($name,"showRepeatEvent", "true"); - addQueue($name,"eventlist","CALEVENT","list","&cal_id_list=[$oids]&start=$tstart&end=$tend&list_repeat=$lr"); - getApiSites($name); - - } else { # Modell Aufgabenliste - my $limit = ""; # Limit of matched tasks - my $offset = 0; # offset of mnatched tasks - my $filterdue = AttrVal($name,"filterDueTask", 3); # show tasks with and without due time - my $filtercomplete = AttrVal($name,"filterCompleteTask", 3); # show completed and not completed tasks - - addQueue($name,"todolist","CALTODO","list","&cal_id_list=[$oids]&limit=$limit&offset=$offset&filter_due=$filterdue&filter_complete=$filtercomplete"); - getApiSites($name); - } - - } elsif ($opt eq "cleanCompleteTasks") { # erledigte Aufgaben einer Cal_id (Liste) löschen - return "Obtain the Calendar list first with \"get $name getCalendars\" command." if(!$hash->{HELPER}{CALFETCHED}); - - my $cals = AttrVal($name,"usedCalendars", ""); - - shift @a; shift @a; - my $c = join(" ", @a); - $cals = $c ? $c : $cals; - return "Please set attribute \"usedCalendars\" or specify the Calendar(s) you want read in \"$opt\" command." if(!$cals); - - # Kalender aufsplitten und zu jedem die ID ermitteln - my @ca = split(",", $cals); - my $oids; - for my $cal (@ca) { - my $oid = $hash->{HELPER}{CALENDARS}{"$cal"}{id}; - next if(!$oid); - if ($hash->{HELPER}{CALENDARS}{"$cal"}{type} ne "ToDo") { - Log3($name, 3, "$name - The Calendar \"$cal\" is not of type \"ToDo\" and will be ignored."); - next; - } - $oids .= "," if($oids); - $oids .= '"'.$oid.'"'; - Log3($name, 2, "$name - WARNING - The Calendar \"$cal\" seems to be unknown because its ID couldn't be found.") if(!$oid); - } - - return "No Calendar of type \"ToDo\" was selected or its ID(s) couldn't be found." if(!$oids); - - Log3($name, 5, "$name - Calendar selection for add queue: $cals"); - - # - addQueue($name,"cleanCompleteTasks","CALTODO","clean_complete","&cal_id_list=[$oids]"); - getApiSites($name); - - } elsif ($opt eq "deleteEventId") { - return "You must specify an event id (Reading EventId) what is to be deleted." if(!$prop); - - my $eventid = $prop; - - # Blocknummer ermitteln - my $bnr; - my @allrds = keys%{$defs{$name}{READINGS}}; - for my $key(@allrds) { - next if $key !~ /_EventId$/x; - $bnr = (split("_", $key))[0] if($defs{$name}{READINGS}{$key}{VAL} == $eventid); # Blocknummer ermittelt - } - - return "The blocknumber of specified event id could not be identified. Make sure you have specified a valid event id." if(!defined $bnr); - - # die Summary zur Event Id ermitteln - my $sum = ReadingsVal($name, $bnr."_01_Summary", ""); - - # Kalendername und dessen id und Typ ermitteln - my $calname = ReadingsVal($name, $bnr."_90_calName", ""); - my $calid = $hash->{HELPER}{CALENDARS}{"$calname"}{id}; - my $caltype = $hash->{HELPER}{CALENDARS}{"$calname"}{type}; - - # Kalender-API in Abhängigkeit des Kalendertyps wählen - my $api = ($caltype eq "Event")?"CALEVENT":"CALTODO"; - - Log3($name, 3, "$name - The event \"$sum\" with id \"$eventid\" will be deleted in calendar \"$calname\"."); - - # - addQueue($name,"deleteEventId",$api,"delete","&evt_id=$eventid"); - getApiSites($name); - - } elsif ($opt eq "restartSendqueue") { - my $ret = getApiSites($name); - if($ret) { - return $ret; - } else { - return "The SendQueue has been restarted."; - } - - } elsif ($opt eq 'eraseReadings') { - delReadings($name,0); # Readings löschen - - } elsif ($opt eq 'logout') { - logout($hash); - - } else { - return "$setlist"; + if($hash->{CREDENTIALS}) { # Model Terminkalender & Aufgabenliste + $setlist .= "calUpdate ". + "credentials ". + "eraseReadings:noArg ". + "listSendqueue:noArg ". + "logout:noArg ". + "restartSendqueue:noArg ". + ($evids ? "deleteEventId:$evids " : "deleteEventId:noArg " ). + ($idxlist ? "purgeSendqueue:-all-,-permError-,$idxlist " : "purgeSendqueue:-all-,-permError- ") + ; } + if($hash->{CREDENTIALS} && $model eq "Tasks") { # Model Aufgabenliste + $setlist .= "cleanCompleteTasks:noArg "; + } + + my $params = { + hash => $hash, + name => $name, + opt => $opt, + prop => $prop, + prop1 => $prop1, + aref => \@a, + }; + + if($hset{$opt} && defined &{$hset{$opt}{fn}}) { + my $ret = q{}; + + if (!$hash->{CREDENTIALS} && $hset{$opt}{needcred}) { + return qq{Credentials of $name are not set. Make sure they are set with "set $name credentials "}; + } + + $ret = &{$hset{$opt}{fn}} ($params); + + return $ret; + } + +return "$setlist"; +} + +###################################################################################### +# Setter credentials +###################################################################################### +sub _setcredentials { + my $paref = shift; + my $hash = $paref->{hash}; + my $name = $paref->{name}; + my $opt = $paref->{opt}; + my $prop = $paref->{prop}; + my $prop1 = $paref->{prop1}; + + return qq{The command "$opt" needs an argument.} if (!$prop); + + if($hash->{HELPER}{SID}) { + logout($hash, $data{SSCal}{$name}{calapi}, $splitstr); # Session alter User beenden falls vorhanden + } + + my ($success) = setCredentials($hash, "credentials", $prop, $prop1, $splitstr); + + if($success) { + addSendqueue ($name,"listcal","CAL","list","&is_todo=true&is_evt=true"); + getApiSites ($name); + return "credentials saved successfully"; + } + else { + return "Error while saving credentials - see logfile for details"; + } + return; } -################################################################ -sub Get { ## no critic 'complexity' +###################################################################################### +# Setter calUpdate +# Termine einer Cal_id (Liste) in Zeitgrenzen abrufen +###################################################################################### +sub _setcalUpdate { + my $paref = shift; + my $hash = $paref->{hash}; + my $name = $paref->{name}; + my $opt = $paref->{opt}; + my $aref = $paref->{aref}; + + if(!$hash->{HELPER}{CALFETCHED}) { + return qq{Obtain the Calendar list first with "get $name getCalendars" command.}; + } + + my $model = $hash->{MODEL}; + my $cals = AttrVal($name,"usedCalendars", ""); + + my @a = @$aref; + shift @a; shift @a; + + my $c = join(" ", @a); + $cals = $c ? $c : $cals; + + if(!$cals) { + return qq{Please set attribute "usedCalendars" or specify the Calendar(s) you want read in "$opt" command.}; + } + + # Kalender aufsplitten und zu jedem die ID ermitteln + my @ca = split(",", $cals); + + my ($oids,@cas); + + my $caltype = $model eq "Diary" ? "Event" : "ToDo"; + + for my $cal (@ca) { + my $oid = $hash->{HELPER}{CALENDARS}{"$cal"}{id}; + next if(!$oid); + + if ($hash->{HELPER}{CALENDARS}{"$cal"}{type} ne $caltype) { + Log3($name, 3, qq{$name - The Calendar "$cal" is not of type "$caltype" and will be ignored.}); + next; + } + + $oids .= "," if($oids); + $oids .= '"'.$oid.'"'; + push (@cas, $cal); + + if(!$oid) { + Log3($name, 2, qq{$name - WARNING - The Calendar "$cal" seems to be unknown because its ID couldn't be found.}); + } + } + + if(!$oids) { + return qq{No Calendar of type "$caltype" was selected or its ID(s) couldn't be found.}; + } + + Log3($name, 5, "$name - Calendar selection for add queue: ".join(',', @cas)); + + if($model eq "Diary") { # Modell Terminkalender + my ($err,$tstart,$tend) = timeEdge ($name); + + if($err) { + Log3($name, 2, "$name - ERROR in timestamp: $err"); + + my $errorcode = "910"; + + readingsBeginUpdate ($hash); + readingsBulkUpdateIfChanged ($hash, "Error", $err); + readingsBulkUpdateIfChanged ($hash, "Errorcode", $errorcode); + readingsBulkUpdate ($hash, "state", "Error"); + readingsEndUpdate ($hash,1); + + return "ERROR in timestamp: $err"; + } + + my $lr = AttrVal($name,"showRepeatEvent", "true"); + addSendqueue ($name,"eventlist","EVENT","list","&cal_id_list=[$oids]&start=$tstart&end=$tend&list_repeat=$lr"); + getApiSites ($name); + } + else { # Modell Aufgabenliste + my $limit = ""; # Limit of matched tasks + my $offset = 0; # offset of mnatched tasks + my $filterdue = AttrVal($name,"filterDueTask", 3); # show tasks with and without due time + my $filtercomplete = AttrVal($name,"filterCompleteTask", 3); # show completed and not completed tasks + + addSendqueue ($name,"todolist","TODO","list","&cal_id_list=[$oids]&limit=$limit&offset=$offset&filter_due=$filterdue&filter_complete=$filtercomplete"); + getApiSites ($name); + } + +return; +} + +###################################################################################### +# Setter cleanCompleteTasks +# erledigte Aufgaben einer Cal_id (Liste) löschen +###################################################################################### +sub _setcleanCompleteTasks { + my $paref = shift; + my $hash = $paref->{hash}; + my $name = $paref->{name}; + my $opt = $paref->{opt}; + my $aref = $paref->{aref}; + + if(!$hash->{HELPER}{CALFETCHED}) { + return qq{Obtain the Calendar list first with "get $name getCalendars" command.}; + } + + my $cals = AttrVal($name,"usedCalendars", ""); + + my @a = @$aref; + shift @a; shift @a; + + my $c = join(" ", @a); + $cals = $c ? $c : $cals; + + if(!$cals) { + return qq{Please set attribute "usedCalendars" to specify the used calendars.}; + } + + # Kalender aufsplitten und zu jedem die ID ermitteln + my @ca = split(",", $cals); + my $oids; + + for my $cal (@ca) { + my $oid = $hash->{HELPER}{CALENDARS}{"$cal"}{id}; + next if(!$oid); + + if ($hash->{HELPER}{CALENDARS}{"$cal"}{type} ne "ToDo") { + Log3($name, 3, qq{$name - The Calendar "$cal" is not of type "ToDo" and will be ignored.}); + next; + } + + $oids .= "," if($oids); + $oids .= '"'.$oid.'"'; + + if(!$oid) { + Log3($name, 2, qq{$name - WARNING - The Calendar "$cal" seems to be unknown because its ID couldn't be found.}); + } + } + + if(!$oids) { + return qq{No Calendar of type "ToDo" was selected or its ID(s) couldn't be found.}; + } + + Log3($name, 5, "$name - Calendar selection for add queue: $cals"); + + # + addSendqueue ($name,"cleanCompleteTasks","TODO","clean_complete","&cal_id_list=[$oids]"); + getApiSites ($name); + +return; +} + +###################################################################################### +# Setter deleteEventId +###################################################################################### +sub _setdeleteEventId { + my $paref = shift; + my $hash = $paref->{hash}; + my $name = $paref->{name}; + my $prop = $paref->{prop} // return "You must specify an event id (Reading EventId) what is to be deleted."; + + my $eventid = $prop; + + # Blocknummer ermitteln + my $bnr; + my @allrds = keys%{$defs{$name}{READINGS}}; + + for my $key(@allrds) { + next if $key !~ /_EventId$/x; + if($defs{$name}{READINGS}{$key}{VAL} == $eventid) { # Blocknummer ermittelt + $bnr = (split("_", $key))[0]; + } + } + + if(!defined $bnr) { + return "The blocknumber of specified event id could not be identified. Make sure you have specified a valid event id."; + } + + # die Summary zur Event Id ermitteln + my $sum = ReadingsVal($name, $bnr."_01_Summary", ""); + + # Kalendername und dessen id und Typ ermitteln + my $calname = ReadingsVal($name, $bnr."_90_calName", ""); + my $calid = $hash->{HELPER}{CALENDARS}{"$calname"}{id}; + my $caltype = $hash->{HELPER}{CALENDARS}{"$calname"}{type}; + + # Kalender-API in Abhängigkeit des Kalendertyps wählen + my $api = ($caltype eq "Event") ? "EVENT" : "TODO"; + + Log3($name, 3, qq{$name - The event "$sum" with id "$eventid" will be deleted in calendar "$calname".}); + + # + addSendqueue ($name,"deleteEventId",$api,"delete","&evt_id=$eventid"); + getApiSites ($name); + +return; +} + +###################################################################################### +# Setter restartSendqueue +###################################################################################### +sub _setrestartSendqueue { + my $paref = shift; + my $name = $paref->{name}; + + my $ret = getApiSites($name); + + if($ret) { + return $ret; + } + else { + return "The SendQueue has been restarted."; + } + +return; +} + +###################################################################################### +# Setter eraseReadings +###################################################################################### +sub _seteraseReadings { + my $paref = shift; + my $name = $paref->{name}; + + delReadings($name, 0); # Readings löschen + +return; +} + +###################################################################################### +# Setter logout +###################################################################################### +sub _setlogout { + my $paref = shift; + my $hash = $paref->{hash}; + my $name = $paref->{name}; + + logout($hash, $data{SSCal}{$name}{calapi}, $splitstr); + +return; +} + +###################################################################################### +# Getter +###################################################################################### +sub Get { ## no critic 'complexity' my ($hash, @a) = @_; return "\"get X\" needs at least an argument" if ( @a < 2 ); my $name = shift @a; @@ -754,9 +843,9 @@ sub Get { my $getlist; if(!$hash->{CREDENTIALS}) { - return; - - } else { + return; + } + else { $getlist = "Unknown argument $opt, choose one of ". "apiInfo:noArg ". "calAsHtml:noArg ". @@ -771,116 +860,52 @@ sub Get { if ($opt eq "storedCredentials") { ## no critic 'Cascading' if (!$hash->{CREDENTIALS}) {return "Credentials of $name are not set - make sure you've set it with \"set $name credentials \"";} # Credentials abrufen - my ($success, $username, $passwd) = getcredentials($hash,0,"credentials"); - unless ($success) {return "Credentials couldn't be retrieved successfully - see logfile"}; + my ($success, $username, $passwd) = getCredentials($hash, 0, "credentials", $splitstr); + + if (!$success) { + return "Credentials couldn't be retrieved successfully - see logfile"; + } return "Stored Credentials:\n". "===================\n". "Username: $username, Password: $passwd \n" - ; - - } elsif ($opt eq "apiInfo") { # Liste aller Kalender abrufen + ; + } + elsif ($opt eq "apiInfo") { # Liste aller Kalender abrufen # übergebenen CL-Hash (FHEMWEB) in Helper eintragen - getclhash($hash,1); - $hash->{HELPER}{APIPARSET} = 0; # Abruf API Infos erzwingen + delClHash ($name); + getClHash ($hash,1); # - addQueue($name,"apiInfo","","",""); - getApiSites($name); - - } elsif ($opt eq "getCalendars") { # Liste aller Kalender abrufen + addSendqueue ($name,"apiInfo","","",""); + getApiSites ($name); + } + elsif ($opt eq "getCalendars") { # Liste aller Kalender abrufen # übergebenen CL-Hash (FHEMWEB) in Helper eintragen - getclhash($hash,1); + delClHash ($name); + getClHash ($hash,1); - addQueue($name,"listcal","CALCAL","list","&is_todo=true&is_evt=true"); - getApiSites($name); - - } elsif ($opt eq "calAsHtml") { + addSendqueue ($name,"listcal","CAL","list","&is_todo=true&is_evt=true"); + getApiSites ($name); + } + elsif ($opt eq "calAsHtml") { my $out = calAsHtml($name); return $out; - - } elsif ($opt =~ /versionNotes/x) { - my $header = "Module release information
"; - my $header1 = "Helpful hints
"; - my %hs; - - # Ausgabetabelle erstellen - my ($val0,$val1); - my $i = 0; - - $ret = ""; - - # Hints - if(!$arg || $arg =~ /hints/x || $arg =~ /[\d]+/x) { - $ret .= sprintf("
$header1
"); - $ret .= ""; - $ret .= ""; - $ret .= ""; - if($arg && $arg =~ /[\d]+/x) { - my @hints = split(",",$arg); - for (@hints) { - if(AttrVal("global","language","EN") eq "DE") { - $hs{$_} = $vHintsExt_de{$_}; - } else { - $hs{$_} = $vHintsExt_en{$_}; - } - } - } else { - if(AttrVal("global","language","EN") eq "DE") { - %hs = %vHintsExt_de; - } else { - %hs = %vHintsExt_en; - } - } - $i = 0; - for my $key (sortTopicNum("desc",keys %hs)) { - $val0 = $hs{$key}; - $ret .= sprintf("" ); - $ret .= ""; - $i++; - if ($i & 1) { - # $i ist ungerade - $ret .= ""; - } else { - $ret .= ""; - } - } - $ret .= ""; - $ret .= ""; - $ret .= "
$key $val0
"; - $ret .= "
"; - } - - # Notes - if(!$arg || $arg =~ /rel/x) { - $ret .= sprintf("
$header
"); - $ret .= ""; - $ret .= ""; - $ret .= ""; - $i = 0; - for my $key (sortTopicNum("desc",keys %vNotesExtern)) { - ($val0,$val1) = split(/\s/x,$vNotesExtern{$key},2); - $ret .= sprintf("" ); - $ret .= ""; - $i++; - if ($i & 1) { - # $i ist ungerade - $ret .= ""; - } else { - $ret .= ""; - } - } - $ret .= ""; - $ret .= ""; - $ret .= "
$key $val0 $val1
"; - $ret .= "
"; - } - - $ret .= ""; - - return $ret; - - } else { + } + elsif ($opt =~ /versionNotes/x) { + my $paref = { + arg => $arg, + }; + + $paref->{hintextde} = \%vHintsExt_de; + $paref->{hintexten} = \%vHintsExt_en; + $paref->{notesext} = \%vNotesExtern; + + $ret = showModuleInfo ($paref); + + return $ret; + } + else { return "$getlist"; } @@ -922,7 +947,8 @@ sub initOnBoot { if ($init_done) { CommandGet(undef, "$name getCalendars"); # Kalender Liste initial abrufen - } else { + } + else { InternalTimer(gettimeofday()+3, "FHEM::SSCal::initOnBoot", $name, 0); } @@ -933,24 +959,29 @@ return; # regelmäßiger Intervallabruf ############################################################################################# sub periodicCall { - my ($name) = @_; + my $name = shift; my $hash = $defs{$name}; + + RemoveInternalTimer($name,"FHEM::SSCal::periodicCall"); + my $interval = AttrVal($name, "interval", 0); - my $model = $hash->{MODEL}; + my $new; if(!$interval) { $hash->{MODE} = "Manual"; - } else { + readingsSingleUpdate($hash, "nextUpdate", "manual", 1); + return; + } + else { $new = gettimeofday()+$interval; readingsBeginUpdate ($hash); readingsBulkUpdate ($hash, "nextUpdate", "Automatic - next polltime: ".FmtTime($new)); # Abrufmode initial auf "Manual" setzen readingsEndUpdate ($hash,1); + + $hash->{MODE} = "Automatic"; } - RemoveInternalTimer($name,"FHEM::SSCal::periodicCall"); - return if(!$interval); - if($hash->{CREDENTIALS} && !IsDisabled($name)) { CommandSet(undef, "$name calUpdate"); # Einträge aller gewählter Kalender oder Aufgabenlisten abrufen (in Queue stellen) } @@ -969,129 +1000,45 @@ return; # $params = spezifische API-Parameter # ###################################################################################### -sub addQueue { - my ($name,$opmode,$api,$method,$params) = @_; - my $hash = $defs{$name}; +sub addSendqueue { + my $name = shift; + my $opmode = shift; + my $api = shift; + my $method = shift; + my $params = shift; - $data{SSCal}{$name}{sendqueue}{index}++; - my $index = $data{SSCal}{$name}{sendqueue}{index}; + my $hash = $defs{$name}; - Log3($name, 5, "$name - Add sendItem to queue - Idx: $index, Opmode: $opmode, API: $api, Method: $method, Params: $params"); - - my $pars = {'opmode' => $opmode, - 'api' => $api, - 'method' => $method, - 'params' => $params, - 'retryCount' => 0 - }; + my $entry = { + 'opmode' => $opmode, + 'api' => $api, + 'method' => $method, + 'params' => $params, + 'retryCount' => 0 + }; - $data{SSCal}{$name}{sendqueue}{entries}{$index} = $pars; - - updQLength ($hash); # updaten Länge der Sendequeue + addSendqueueEntry ($hash, $entry); # den Datensatz zur Sendqueue hinzufügen # updaten Länge der Sendequeue return; } - -############################################################################################# -# Erfolg einer Rückkehrroutine checken und ggf. Send-Retry ausführen -# bzw. den SendQueue-Eintrag bei Erfolg löschen -# $name = Name des calbot-Devices -# $retry = 0 -> Opmode erfolgreich (DS löschen), -# 1 -> Opmode nicht erfolgreich (Abarbeitung nach ckeck errorcode -# eventuell verzögert wiederholen) -############################################################################################# -sub checkRetry { - my ($name,$retry) = @_; - my $hash = $defs{$name}; - my $idx = $hash->{OPIDX}; - my $forbidSend = ""; - - if(!keys %{$data{SSCal}{$name}{sendqueue}{entries}}) { - Log3($name, 4, "$name - SendQueue is empty. Nothing to do ..."); - updQLength ($hash); - return; - } - - if(!$retry) { # Befehl erfolgreich, Senden nur neu starten wenn weitere Einträge in SendQueue - delete $hash->{OPIDX}; - delete $data{SSCal}{$name}{sendqueue}{entries}{$idx}; - Log3($name, 4, "$name - Opmode \"$hash->{OPMODE}\" finished successfully, Sendqueue index \"$idx\" deleted."); - updQLength ($hash); - return getApiSites($name); # nächsten Eintrag abarbeiten (wenn SendQueue nicht leer) - - } else { # Befehl nicht erfolgreich, (verzögertes) Senden einplanen - $data{SSCal}{$name}{sendqueue}{entries}{$idx}{retryCount}++; - my $rc = $data{SSCal}{$name}{sendqueue}{entries}{$idx}{retryCount}; - - my $errorcode = ReadingsVal($name, "Errorcode", 0); - if($errorcode =~ /119/x) { - delete $hash->{HELPER}{SID}; - } - if($errorcode =~ /100|101|103|117|120|407|409|800|900/x) { # bei diesen Errorcodes den Queueeintrag nicht wiederholen, da dauerhafter Fehler ! - $forbidSend = experror($hash,$errorcode); # Fehlertext zum Errorcode ermitteln - $data{SSCal}{$name}{sendqueue}{entries}{$idx}{forbidSend} = $forbidSend; - - Log3($name, 2, "$name - ERROR - \"$hash->{OPMODE}\" SendQueue index \"$idx\" not executed. It seems to be a permanent error. Exclude it from new send attempt !"); - - delete $hash->{OPIDX}; - delete $hash->{OPMODE}; - - updQLength ($hash); # updaten Länge der Sendequeue - - return getApiSites($name); # nächsten Eintrag abarbeiten (wenn SendQueue nicht leer); - } - - if(!$forbidSend) { - my $rs = 0; - if($rc <= 1) { - $rs = 5; - } elsif ($rc < 3) { - $rs = 20; - } elsif ($rc < 5) { - $rs = 60; - } elsif ($rc < 7) { - $rs = 1800; - } elsif ($rc < 30) { - $rs = 3600; - } else { - $rs = 86400; - } - - Log3($name, 2, "$name - ERROR - \"$hash->{OPMODE}\" SendQueue index \"$idx\" not executed. Restart SendQueue in $rs seconds (retryCount $rc)."); - - my $rst = gettimeofday()+$rs; # resend Timer - updQLength ($hash,$rst); # updaten Länge der Sendequeue mit resend Timer - - RemoveInternalTimer($name, "FHEM::SSCal::getApiSites"); - InternalTimer($rst, "FHEM::SSCal::getApiSites", "$name", 0); - } - } - -return; -} - -############################################################################################################################# -####### Begin Kameraoperationen mit NonblockingGet (nicht blockierender HTTP-Call) ####### -############################################################################################################################# +#################################################################################### +# Einstiegsfunktion Queue Abarbeitung +#################################################################################### sub getApiSites { - my ($name) = @_; - my $hash = $defs{$name}; - my $addr = $hash->{ADDR}; - my $port = $hash->{PORT}; - my $prot = $hash->{PROT}; - my ($url,$param,$idxset,$ret); + my $name = shift; + my $hash = $defs{$name}; + my $addr = $hash->{SERVERADDR}; + my $port = $hash->{SERVERPORT}; + my $prot = $hash->{PROTOCOL}; + + my ($url,$idxset,$ret); $hash->{HELPER}{LOGINRETRIES} = 0; my ($err,$tstart,$tend) = timeEdge($name); $tstart = FmtDateTime($tstart); - $tend = FmtDateTime($tend); - - # API-Pfade und MaxVersions ermitteln - Log3($name, 4, "$name - ####################################################"); - Log3($name, 4, "$name - ### start Synology Calendar operation "); - Log3($name, 4, "$name - ####################################################"); + $tend = FmtDateTime($tend); if(!keys %{$data{SSCal}{$name}{sendqueue}{entries}}) { $ret = "Sendqueue is empty. Nothing to do ..."; @@ -1109,47 +1056,66 @@ sub getApiSites { } } + Log3($name, 4, "$name - ####################################################"); + Log3($name, 4, "$name - ### start Calendar operation $hash->{OPMODE} "); + Log3($name, 4, "$name - ####################################################"); + if(!$idxset) { - $ret = "Only entries with \"forbidSend\" are in Sendqueue. Escaping ..."; + $ret = qq{Only entries with "forbidSend" are in Sendqueue. Escaping ...}; Log3($name, 4, "$name - $ret"); return $ret; } - readingsBeginUpdate ($hash); - readingsBulkUpdate ($hash, "state", "running"); - readingsEndUpdate ($hash,1); + readingsBeginUpdate ($hash); + readingsBulkUpdate ($hash, "state", "running"); + readingsEndUpdate ($hash,1); Log3($name, 4, "$name - Time selection start: ".$tstart); Log3($name, 4, "$name - Time selection end: ".$tend); - if ($hash->{HELPER}{APIPARSET}) { # API-Hashwerte sind bereits gesetzt -> Abruf überspringen + if ($hash->{OPMODE} eq "apiInfo") { + $data{SSCal}{$name}{calapi}{PARSET} = 0; # erzwinge Abruf API + } + + if ($data{SSCal}{$name}{calapi}{PARSET}) { # API-Hashwerte sind bereits gesetzt -> Abruf überspringen Log3($name, 4, "$name - API hash values already set - ignore get apisites"); return checkSID($name); } my $timeout = AttrVal($name,"timeout",20); Log3($name, 5, "$name - HTTP-Call will be done with timeout: $timeout s"); + + # API initialisieren und abrufen + #################################### + $data{SSCal}{$name}{calapi} = apistatic ("calendar"); # API Template im HELPER instanziieren + + Log3 ($name, 4, "$name - API imported:\n".Dumper $data{SSCal}{$name}{calapi}); + + my @ak; + for my $key (keys %{$data{SSCal}{$name}{calapi}}) { + next if($key =~ /^PARSET$/x); + push @ak, $data{SSCal}{$name}{calapi}{$key}{NAME}; + } + my $apis = join ",", @ak; - # URL zur Abfrage der Eigenschaften der API's my $calapi = $data{SSCal}{$name}{calapi}; - $url = "$prot://$addr:$port/webapi/query.cgi?api=".$calapi->{APIINFO}{NAME}."&method=Query&version=1&query=". - $calapi->{APIAUTH}{NAME}.",". - $calapi->{CALCAL}{NAME}.",". - $calapi->{CALEVENT}{NAME}.",". - $calapi->{CALSHARE}{NAME}.",". - $calapi->{CALTODO}{NAME}.",". - $calapi->{APIINFO}{NAME}; + $url = "$prot://$addr:$port/webapi/$data{SSCal}{$name}{calapi}{INFO}{PATH}?". + "api=$data{SSCal}{$name}{calapi}{INFO}{NAME}". + "&method=Query". + "&version=$data{SSCal}{$name}{calapi}{INFO}{VER}". + "&query=$apis"; Log3($name, 4, "$name - Call-Out: $url"); - $param = { - url => $url, - timeout => $timeout, - hash => $hash, - method => "GET", - header => "Accept: application/json", - callback => \&FHEM::SSCal::getApiSites_parse - }; + my $param = { + url => $url, + timeout => $timeout, + hash => $hash, + method => "GET", + header => "Accept: application/json", + callback => \&FHEM::SSCal::getApiSites_parse + }; + HttpUtils_NonblockingGet ($param); return; @@ -1158,196 +1124,90 @@ return; #################################################################################### # Auswertung Abruf apisites #################################################################################### -sub getApiSites_parse { ## no critic 'complexity' +sub getApiSites_parse { ## no critic 'complexity' my ($param, $err, $myjson) = @_; my $hash = $param->{hash}; my $name = $hash->{NAME}; - my $addr = $hash->{ADDR}; - my $port = $hash->{PORT}; my $opmode = $hash->{OPMODE}; my ($error,$errorcode,$success); - if ($err ne "") { - # wenn ein Fehler bei der HTTP Abfrage aufgetreten ist + if ($err ne "") { # wenn ein Fehler bei der HTTP Abfrage aufgetreten ist Log3($name, 2, "$name - ERROR message: $err"); - readingsBeginUpdate ($hash); - readingsBulkUpdateIfChanged ($hash, "Error", $err); - readingsBulkUpdateIfChanged ($hash, "Errorcode", "none"); - readingsBulkUpdate ($hash, "state", "Error"); - readingsEndUpdate ($hash,1); - - checkRetry($name,1); + setReadingErrorState ($hash, $err); + checkSendRetry ($name, 1, $queueStartFn); return; + } + elsif ($myjson ne "") { + ($success) = evaljson($hash,$myjson); - } elsif ($myjson ne "") { - # Evaluiere ob Daten im JSON-Format empfangen wurden - ($hash,$success,$myjson) = evaljson($hash,$myjson); - unless ($success) { - Log3($name, 4, "$name - Data returned: ".$myjson); - checkRetry($name,1); + if (!$success) { + Log3 ($name, 4, "$name - Data returned: ".$myjson); + checkSendRetry ($name, 1, $queueStartFn); return; } - my $data = decode_json($myjson); + my $jdata = decode_json($myjson); - # Logausgabe decodierte JSON Daten - Log3($name, 5, "$name - JSON returned: ". Dumper $data); + Log3($name, 5, "$name - JSON returned: ". Dumper $jdata); - $success = $data->{'success'}; + $success = $jdata->{'success'}; if ($success) { - my $logstr; - my $calapi = $data{SSCal}{$name}{calapi}; - - # Pfad und Maxversion von "SYNO.API.Auth" ermitteln - my $apiauthpath = $data->{data}->{$calapi->{APIAUTH}{NAME}}->{path}; - $apiauthpath =~ tr/_//d if (defined($apiauthpath)); - my $apiauthmaxver = $data->{data}->{$calapi->{APIAUTH}{NAME}}->{maxVersion}; - - $logstr = defined($apiauthpath) ? "Path of $calapi->{APIAUTH}{NAME} selected: $apiauthpath" : "Path of $calapi->{APIAUTH}{NAME} undefined - Synology cal Server may be stopped"; - Log3($name, 4, "$name - $logstr"); - $logstr = defined($apiauthmaxver) ? "MaxVersion of $calapi->{APIAUTH}{NAME} selected: $apiauthmaxver" : "MaxVersion of $calapi->{APIAUTH}{NAME} undefined - Synology cal Server may be stopped"; - Log3($name, 4, "$name - $logstr"); - - # Pfad und Maxversion von "SYNO.Cal.Cal" ermitteln - my $apicalpath = $data->{data}->{$calapi->{CALCAL}{NAME}}->{path}; - $apicalpath =~ tr/_//d if (defined($apicalpath)); - my $apicalmaxver = $data->{data}->{$calapi->{CALCAL}{NAME}}->{maxVersion}; - - $logstr = defined($apicalpath) ? "Path of $calapi->{CALCAL}{NAME} selected: $apicalpath" : "Path of $calapi->{CALCAL}{NAME} undefined - Synology cal Server may be stopped"; - Log3($name, 4, "$name - $logstr"); - $logstr = defined($apicalmaxver) ? "MaxVersion of $calapi->{CALCAL}{NAME} selected: $apicalmaxver" : "MaxVersion of $calapi->{CALCAL}{NAME} undefined - Synology cal Server may be stopped"; - Log3($name, 4, "$name - $logstr"); - - # Pfad und Maxversion von "SYNO.Cal.Event" ermitteln - my $apievtpath = $data->{data}->{$calapi->{CALEVENT}{NAME}}->{path}; - $apievtpath =~ tr/_//d if (defined($apievtpath)); - my $apievtmaxver = $data->{data}->{$calapi->{CALEVENT}{NAME}}->{maxVersion}; - - $logstr = defined($apievtpath) ? "Path of $calapi->{CALEVENT}{NAME} selected: $apievtpath" : "Path of $calapi->{CALEVENT}{NAME} undefined - Synology cal Server may be stopped"; - Log3($name, 4, "$name - $logstr"); - $logstr = defined($apievtmaxver) ? "MaxVersion of $calapi->{CALEVENT}{NAME} selected: $apievtmaxver" : "MaxVersion of $calapi->{CALEVENT}{NAME} undefined - Synology cal Server may be stopped"; - Log3($name, 4, "$name - $logstr"); + my $completed = completeAPI ($jdata, $data{SSCal}{$name}{calapi}); # übergibt Referenz zum instanziierten API-Hash - # Pfad und Maxversion von "SYNO.Cal.Sharing" ermitteln - my $apisharepath = $data->{data}->{$calapi->{CALSHARE}{NAME}}->{path}; - $apisharepath =~ tr/_//d if (defined($apisharepath)); - my $apisharemaxver = $data->{data}->{$calapi->{CALSHARE}{NAME}}->{maxVersion}; - - $logstr = defined($apisharepath) ? "Path of $calapi->{CALSHARE}{NAME} selected: $apisharepath" : "Path of $calapi->{CALSHARE}{NAME} undefined - Synology cal Server may be stopped"; - Log3($name, 4, "$name - $logstr"); - $logstr = defined($apisharemaxver) ? "MaxVersion of $calapi->{CALSHARE}{NAME} selected: $apisharemaxver" : "MaxVersion of $calapi->{CALSHARE}{NAME} undefined - Synology cal Server may be stopped"; - Log3($name, 4, "$name - $logstr"); - - # Pfad und Maxversion von "SYNO.Cal.Todo" ermitteln - my $apitodopath = $data->{data}->{$calapi->{CALTODO}{NAME}}->{path}; - $apitodopath =~ tr/_//d if (defined($apitodopath)); - my $apitodomaxver = $data->{data}->{$calapi->{CALTODO}{NAME}}->{maxVersion}; - - $logstr = defined($apitodopath) ? "Path of $calapi->{CALTODO}{NAME} selected: $apitodopath" : "Path of $calapi->{CALTODO}{NAME} undefined - Synology cal Server may be stopped"; - Log3($name, 4, "$name - $logstr"); - $logstr = defined($apitodomaxver) ? "MaxVersion of $calapi->{CALTODO}{NAME} selected: $apitodomaxver" : "MaxVersion of $calapi->{CALTODO}{NAME} undefined - Synology cal Server may be stopped"; - Log3($name, 4, "$name - $logstr"); - - # Pfad und Maxversion von "SYNO.API.Info" ermitteln - my $apiinfopath = $data->{data}->{$calapi->{APIINFO}{NAME}}->{path}; - $apiinfopath =~ tr/_//d if (defined($apiinfopath)); - my $apiinfomaxver = $data->{data}->{$calapi->{APIINFO}{NAME}}->{maxVersion}; - - $logstr = defined($apiinfopath) ? "Path of $calapi->{APIINFO}{NAME} selected: $apiinfopath" : "Path of $calapi->{APIINFO}{NAME} undefined - Synology cal Server may be stopped"; - Log3($name, 4, "$name - $logstr"); - $logstr = defined($apiinfomaxver) ? "MaxVersion of $calapi->{APIINFO}{NAME} selected: $apiinfomaxver" : "MaxVersion of $calapi->{APIINFO}{NAME} undefined - Synology cal Server may be stopped"; - Log3($name, 4, "$name - $logstr"); + if(!$completed) { + $errorcode = "9001"; + $error = expErrors($hash,$errorcode); # Fehlertext zum Errorcode ermitteln + + setReadingErrorState ($hash, $error, $errorcode); + Log3($name, 2, "$name - ERROR - $error"); + + checkSendRetry ($name, 1, $queueStartFn); + return; + } - - # Downgrades für nicht kompatible API-Versionen - # hier nur nutzen wenn API zentral downgraded werden soll - my ($infomod, $authmod, $calmod, $evtmod, $sharemod, $todomod); + # Downgrades für nicht kompatible API-Versionen. Hier nur nutzen wenn API zentral downgraded werden soll Log3($name, 4, "$name - ------- Begin of adaption section -------"); - $apievtmaxver = 3; - $evtmod = "yes"; - Log3($name, 4, "$name - MaxVersion of $calapi->{CALEVENT}{NAME} adapted to: $apievtmaxver"); + my @sims; + + push @sims, "EVENT:3"; + + for my $esim (@sims) { + my($k,$v) = split ":", $esim; + $data{SSCal}{$name}{calapi}{$k}{VER} = $v; + $data{SSCal}{$name}{calapi}{$k}{MOD} = "yes"; + Log3($name, 4, "$name - Version of $data{SSCal}{$name}{calapi}{$k}{NAME} adapted to: $data{SSCal}{$name}{calapi}{$k}{VER}"); + } Log3($name, 4, "$name - ------- End of adaption section -------"); - # ermittelte Werte in $hash einfügen - $calapi->{APIINFO}{PATH} = $apiinfopath; - $calapi->{APIINFO}{MAX} = $apiinfomaxver; - $calapi->{APIINFO}{MOD} = $infomod // "no"; - $calapi->{APIAUTH}{PATH} = $apiauthpath; - $calapi->{APIAUTH}{MAX} = $apiauthmaxver; - $calapi->{APIAUTH}{MOD} = $authmod // "no"; - $calapi->{CALCAL}{PATH} = $apicalpath; - $calapi->{CALCAL}{MAX} = $apicalmaxver; - $calapi->{CALCAL}{MOD} = $calmod // "no"; - $calapi->{CALEVENT}{PATH} = $apievtpath; - $calapi->{CALEVENT}{MAX} = $apievtmaxver; - $calapi->{CALEVENT}{MOD} = $evtmod // "no"; - $calapi->{CALSHARE}{PATH} = $apisharepath; - $calapi->{CALSHARE}{MAX} = $apisharemaxver; - $calapi->{CALSHARE}{MOD} = $sharemod // "no"; - $calapi->{CALTODO}{PATH} = $apitodopath; - $calapi->{CALTODO}{MAX} = $apitodomaxver; - $calapi->{CALTODO}{MOD} = $todomod // "no"; - - # API values sind gesetzt in Hash - $hash->{HELPER}{APIPARSET} = 1; - - if ($opmode eq "apiInfo") { # API Infos in Popup anzeigen - my $out = ""; - $out .= "Synology Calendar API Info

"; - $out .= ""; - $out .= ""; - $out .= ""; - - for my $key (keys %{$calapi}) { - my $apiname = $calapi->{$key}{NAME}; - my $apipath = $calapi->{$key}{PATH}; - my $apiver = $calapi->{$key}{MAX}; - my $apimod = $calapi->{$key}{MOD}; + setReadingErrorNone($hash, 1); - $out .= ""; - $out .= ""; - $out .= ""; - $out .= ""; - $out .= ""; - $out .= ""; - } + Log3 ($name, 4, "$name - API completed:\n".Dumper $data{SSCal}{$name}{calapi}); - $out .= "
API Path Version Downgraded
$apiname $apipath $apiver $apimod
"; - $out .= ""; - - readingsBeginUpdate ($hash); - readingsBulkUpdateIfChanged ($hash,"Errorcode","none"); - readingsBulkUpdateIfChanged ($hash,"Error", "none"); - readingsBulkUpdate ($hash, "state", "done"); - readingsEndUpdate ($hash,1); - - # Ausgabe Popup der User-Daten (nach readingsEndUpdate positionieren sonst - # "Connection lost, trying reconnect every 5 seconds" wenn > 102400 Zeichen) - asyncOutput($hash->{HELPER}{CL}{1},"$out"); - delete($hash->{HELPER}{CL}); - - checkRetry($name,0); + if ($opmode eq "apiInfo") { # API Infos in Popup anzeigen + showAPIinfo ($hash, $data{SSCal}{$name}{calapi}); # übergibt Referenz zum instanziierten API-Hash) + readingsSingleUpdate ($hash, "state", "done", 1); + checkSendRetry ($name, 0, $queueStartFn); return; - } - - } else { + } + } + else { $errorcode = "806"; - $error = experror($hash,$errorcode); # Fehlertext zum Errorcode ermitteln + $error = expErrors($hash,$errorcode); # Fehlertext zum Errorcode ermitteln readingsBeginUpdate ($hash); readingsBulkUpdateIfChanged ($hash, "Errorcode", $errorcode); readingsBulkUpdateIfChanged ($hash, "Error", $error); - readingsBulkUpdate ($hash,"state", "Error"); + readingsBulkUpdate ($hash, "state", "Error"); readingsEndUpdate ($hash, 1); Log3($name, 2, "$name - ERROR - the API-Query couldn't be executed successfully"); - checkRetry($name,1); + checkSendRetry ($name, 1, $queueStartFn); return; } } @@ -1361,9 +1221,9 @@ return checkSID($name); sub calOp { my ($name) = @_; my $hash = $defs{$name}; - my $prot = $hash->{PROT}; - my $addr = $hash->{ADDR}; - my $port = $hash->{PORT}; + my $prot = $hash->{PROTOCOL}; + my $addr = $hash->{SERVERADDR}; + my $port = $hash->{SERVERPORT}; my $sid = $hash->{HELPER}{SID}; my ($url,$timeout,$param,$error,$errorcode); @@ -1380,16 +1240,17 @@ sub calOp { Log3($name, 5, "$name - HTTP-Call will be done with timeout: $timeout s"); - $url = "$prot://$addr:$port/webapi/".$calapi->{$api}{PATH}."?api=".$calapi->{$api}{NAME}."&version=".$calapi->{$api}{MAX}."&method=$method".$params."&_sid=$sid"; + $url = "$prot://$addr:$port/webapi/".$calapi->{$api}{PATH}."?api=".$calapi->{$api}{NAME}."&version=".$calapi->{$api}{VER}."&method=$method".$params."&_sid=$sid"; - if($opmode eq "deleteEventId" && $api eq "CALEVENT") { # Workaround !!! Methode delete funktioniert nicht mit SYNO.Cal.Event version > 1 + if($opmode eq "deleteEventId" && $api eq "EVENT") { # Workaround !!! Methode delete funktioniert nicht mit SYNO.Cal.Event version > 1 $url = "$prot://$addr:$port/webapi/".$calapi->{$api}{PATH}."?api=".$calapi->{$api}{NAME}."&version=1&method=$method".$params."&_sid=$sid"; } my $part = $url; if(AttrVal($name, "showPassInLog", "0") == 1) { Log3($name, 4, "$name - Call-Out: $url"); - } else { + } + else { $part =~ s/$sid//x; Log3($name, 4, "$name - Call-Out: $part"); } @@ -1411,19 +1272,18 @@ return; ############################################################################################# # Callback from calOp ############################################################################################# -sub calOp_parse { ## no critic 'complexity' +sub calOp_parse { ## no critic 'complexity' my ($param, $err, $myjson) = @_; my $hash = $param->{hash}; my $name = $hash->{NAME}; - my $prot = $hash->{PROT}; - my $addr = $hash->{ADDR}; - my $port = $hash->{PORT}; + my $prot = $hash->{PROTOCOL}; + my $addr = $hash->{SERVERADDR}; + my $port = $hash->{SERVERPORT}; my $opmode = $hash->{OPMODE}; my $am = AttrVal($name, "asyncMode", 0); my ($ts,$data,$success,$error,$errorcode,$cherror,$r); - if ($err ne "") { - # wenn ein Fehler bei der HTTP Abfrage aufgetreten ist + if ($err ne "") { # wenn ein Fehler bei der HTTP Abfrage aufgetreten ist Log3($name, 2, "$name - ERROR message: $err"); $errorcode = "none"; @@ -1435,22 +1295,20 @@ sub calOp_parse { ## n readingsBulkUpdate ($hash, "state", "Error"); readingsEndUpdate ($hash,1); - checkRetry($name,1); + checkSendRetry ($name, 1, $queueStartFn); return; - - } elsif ($myjson ne "") { - # wenn die Abfrage erfolgreich war ($data enthält die Ergebnisdaten des HTTP Aufrufes) - # Evaluiere ob Daten im JSON-Format empfangen wurden - ($hash,$success,$myjson) = evaljson($hash,$myjson); - unless ($success) { - Log3($name, 4, "$name - Data returned: ".$myjson); - checkRetry($name,1); + } + elsif ($myjson ne "") { # wenn die Abfrage erfolgreich war ($data enthält die Ergebnisdaten des HTTP Aufrufes) + ($success) = evaljson($hash,$myjson); + + if (!$success) { + Log3 ($name, 4, "$name - Data returned: ".$myjson); + checkSendRetry ($name, 1, $queueStartFn); return; } $data = decode_json($myjson); - # Logausgabe decodierte JSON Daten Log3($name, 5, "$name - JSON returned: ". Dumper $data); $success = $data->{'success'}; @@ -1516,15 +1374,15 @@ sub calOp_parse { ## n asyncOutput($hash->{HELPER}{CL}{1},"$out"); delete($hash->{HELPER}{CL}); - checkRetry($name,0); + checkSendRetry ($name, 0, $queueStartFn); readingsBeginUpdate ($hash); readingsBulkUpdateIfChanged ($hash, "Errorcode", "none"); readingsBulkUpdateIfChanged ($hash, "Error", "none"); readingsBulkUpdate ($hash, "state", "done"); readingsEndUpdate ($hash,1); - - } elsif ($opmode eq "eventlist") { # Events der ausgewählten Kalender aufbereiten + } + elsif ($opmode eq "eventlist") { # Events der ausgewählten Kalender aufbereiten delete $data{SSCal}{$name}{eventlist}; # zentrales Event/ToDo Hash löschen delete $data{SSCal}{$name}{vcalendar}; # zentrales VCALENDAR Hash löschen $hash->{eventlist} = $data; # Data-Hashreferenz im Hash speichern @@ -1534,31 +1392,30 @@ sub calOp_parse { ## n my $timeout = AttrVal($name, "timeout", 20)+180; $hash->{HELPER}{RUNNING_PID} = BlockingCall("FHEM::SSCal::extractEventlist", $name, "FHEM::SSCal::createReadings", $timeout, "FHEM::SSCal::blockingTimeout", $hash); - $hash->{HELPER}{RUNNING_PID}{loglevel} = 5 if($hash->{HELPER}{RUNNING_PID}); # Forum #77057 - - } else { # Extrahieren der Events synchron (blockierend) + $hash->{HELPER}{RUNNING_PID}{loglevel} = 5 if($hash->{HELPER}{RUNNING_PID}); # Forum #77057 + } + else { # Extrahieren der Events synchron (blockierend) Log3($name, 4, "$name - Event parse mode: synchronous"); extractEventlist ($name); - } - - } elsif ($opmode eq "todolist") { # ToDo's der ausgewählten Tasks-Kalender aufbereiten - delete $data{SSCal}{$name}{eventlist}; # zentrales Event/ToDo Hash löschen - $hash->{eventlist} = $data; # Data-Hashreferenz im Hash speichern + } + } + elsif ($opmode eq "todolist") { # ToDo's der ausgewählten Tasks-Kalender aufbereiten + delete $data{SSCal}{$name}{eventlist}; # zentrales Event/ToDo Hash löschen + $hash->{eventlist} = $data; # Data-Hashreferenz im Hash speichern - if ($am) { # Extrahieren der ToDos asynchron (nicht-blockierend) + if ($am) { # Extrahieren der ToDos asynchron (nicht-blockierend) Log3($name, 4, "$name - Task parse mode: asynchronous"); my $timeout = AttrVal($name, "timeout", 20)+180; $hash->{HELPER}{RUNNING_PID} = BlockingCall("FHEM::SSCal::extractToDolist", $name, "FHEM::SSCal::createReadings", $timeout, "FHEM::SSCal::blockingTimeout", $hash); $hash->{HELPER}{RUNNING_PID}{loglevel} = 5 if($hash->{HELPER}{RUNNING_PID}); # Forum #77057 - - } else { # Extrahieren der ToDos synchron (blockierend) + } + else { # Extrahieren der ToDos synchron (blockierend) Log3($name, 4, "$name - Task parse mode: synchronous"); extractToDolist ($name); - } - - } elsif ($opmode eq "cleanCompleteTasks") { # abgeschlossene ToDos wurden gelöscht - + } + } + elsif ($opmode eq "cleanCompleteTasks") { # abgeschlossene ToDos wurden gelöscht readingsBeginUpdate ($hash); readingsBulkUpdateIfChanged ($hash, "Errorcode", "none"); readingsBulkUpdateIfChanged ($hash, "Error", "none"); @@ -1567,10 +1424,9 @@ sub calOp_parse { ## n Log3($name, 3, "$name - All completed tasks were deleted from selected ToDo lists"); - checkRetry($name,0); - - } elsif ($opmode eq "deleteEventId") { # ein Kalendereintrag mit Event Id wurde gelöscht - + checkSendRetry ($name, 0, $queueStartFn); + } + elsif ($opmode eq "deleteEventId") { # ein Kalendereintrag mit Event Id wurde gelöscht readingsBeginUpdate ($hash); readingsBulkUpdateIfChanged ($hash, "Errorcode", "none"); readingsBulkUpdateIfChanged ($hash, "Error", "none"); @@ -1583,19 +1439,19 @@ sub calOp_parse { ## n my $idx = $hash->{OPIDX}; my $api = $data{SSCal}{$name}{sendqueue}{entries}{$idx}{api}; - checkRetry($name,0); + checkSendRetry ($name, 0, $queueStartFn); # Kalendereinträge neu einlesen nach dem löschen Event Id - CommandSet(undef, "$name calUpdate"); - - } - - } else { + CommandSet(undef, "$name calUpdate"); + } + } + else { # die API-Operation war fehlerhaft # Errorcode aus JSON ermitteln $errorcode = $data->{error}->{code}; $cherror = $data->{error}->{errors}; # vom cal gelieferter Fehler - $error = experror($hash,$errorcode); # Fehlertext zum Errorcode ermitteln + $error = expErrors($hash,$errorcode); # Fehlertext zum Errorcode ermitteln + if ($error =~ /not found/) { $error .= " New error: ".($cherror // "' '"); } @@ -1608,7 +1464,7 @@ sub calOp_parse { ## n Log3($name, 2, "$name - ERROR - Operation $opmode was not successful. Errorcode: $errorcode - $error"); - checkRetry($name,1); + checkSendRetry ($name, 1, $queueStartFn); } undef $data; @@ -1680,11 +1536,13 @@ sub extractEventlist { ## no critic 'complexi Log3($name, 4, "$name - Ignore single event -> $data->{data}{$key}[$i]{summary} start: $bdate $btime, end: $edate $etime"); $ignore = 1; $done = 0; - } elsif ($excl) { + } + elsif ($excl) { Log3($name, 4, "$name - Ignored by Ical compare -> $data->{data}{$key}[$i]{summary} start: $bdate $btime, end: $edate $etime"); $ignore = 1; $done = 0; - } else { + } + else { writeValuesToArray ({ name => $name, eventno => $n, calref => $data->{data}{$key}[$i], @@ -1700,9 +1558,9 @@ sub extractEventlist { ## no critic 'complexi }); $ignore = 0; $done = 1; - } - - } elsif ($data->{data}{$key}[$i]{is_repeat_evt}) { # Event ist wiederholend + } + } + elsif ($data->{data}{$key}[$i]{is_repeat_evt}) { # Event ist wiederholend Log3($name, 5, "$name - Recurring event Begin: $bdate, End: $edate"); my ($freq,$count,$interval,$until,$uets,$bymonthday,$byday); @@ -1715,17 +1573,22 @@ sub extractEventlist { ## no critic 'complexi my ($p1,$p2) = split("=", $par); if ($p1 eq "FREQ") { $freq = $p2; - } elsif ($p1 eq "COUNT") { # Event endet automatisch nach x Wiederholungen + } + elsif ($p1 eq "COUNT") { # Event endet automatisch nach x Wiederholungen $count = $p2; - } elsif ($p1 eq "INTERVAL") { # Wiederholungsintervall + } + elsif ($p1 eq "INTERVAL") { # Wiederholungsintervall $interval = $p2; - } elsif ($p1 eq "UNTIL") { # festes Intervallende angegeben + } + elsif ($p1 eq "UNTIL") { # festes Intervallende angegeben $until = $p2; $until =~ s/[-:]//gx; $uets = (explodeDateTime ($hash, $until, 0, 0, 0))[4]; - } elsif ($p1 eq "BYMONTHDAY") { # Wiederholungseigenschaft -> Tag des Monats z.B. 13 (Tag 13) + } + elsif ($p1 eq "BYMONTHDAY") { # Wiederholungseigenschaft -> Tag des Monats z.B. 13 (Tag 13) $bymonthday = $p2; - } elsif ($p1 eq "BYDAY") { # Wiederholungseigenschaft -> Wochentag z.B. 2WE,-1SU,4FR (kann auch Liste bei WEEKLY sein) + } + elsif ($p1 eq "BYDAY") { # Wiederholungseigenschaft -> Wochentag z.B. 2WE,-1SU,4FR (kann auch Liste bei WEEKLY sein) $byday = $p2; } } @@ -1842,6 +1705,7 @@ sub extractEventlist { ## no critic 'complexi last if((defined $uets && ($uets < $nbts)) || $nbts > $tend); } } + if ($byday) { # Wiederholungseigenschaft -> Wochentag z.B. 2WE,-1SU,4FR (kann auch Liste bei WEEKLY sein) my ($nbhh,$nbmm,$nbss,$nehh,$nemm,$ness,$rDayOfWeekNew,$rDaysToAddOrSub,$rNewTime); my @ByDays = split(",", $byday); # Array der Wiederholungstage @@ -1855,7 +1719,8 @@ sub extractEventlist { ## no critic 'complexi if ($rByDayLength > 2) { $rDayStr = substr($rByDay, -2); $rDayInterval = int(substr($rByDay, 0, $rByDayLength - 2)); - } else { + } + else { $rDayStr = $rByDay; $rDayInterval = 1; } @@ -1876,15 +1741,16 @@ sub extractEventlist { ## no critic 'complexi if ($rDayOfWeekNew <= $rDayOfWeek) { # Nr Wochentag des 1. des Monats <= als Wiederholungstag $rDaysToAddOrSub = $rDayOfWeek - $rDayOfWeekNew; - } else { + } + else { $rDaysToAddOrSub = 7 - $rDayOfWeekNew + $rDayOfWeek; } $rDaysToAddOrSub += (7 * ($rDayInterval - 1)); # addiere Tagesintervall, z.B. 4th Freitag ... $rNewTime = plusNSeconds($firstOfNextMonth, 86400*$rDaysToAddOrSub, 1); ($nbss,$nbmm,$nbhh,$bmday,$bmonth,$byear,$ness,$nemm,$nehh,$emday,$emonth,$eyear) = DTfromStartandDiff ($rNewTime,$startEndDiff); - - } else { + } + else { Log3($name, 2, "$name - WARNING - negative values for BYDAY are currently not implemented and will be ignored"); $ignore = 1; $done = 0; @@ -1955,7 +1821,8 @@ sub extractEventlist { ## no critic 'complexi if ($rDayOfWeekNew <= $rDayOfWeek) { # Nr nächster Wochentag <= Planwochentag $rDaysToAddOrSub = $rDayOfWeek - $rDayOfWeekNew; - } else { + } + else { $rDaysToAddOrSub = 7 - $rDayOfWeekNew + $rDayOfWeek + (7 * ($interval-1)); } @@ -2001,15 +1868,15 @@ sub extractEventlist { ## no critic 'complexi last if((defined $uets && ($uets < $nbts)) || $nbts > $tend || $ci == $count); } last if((defined $uets && ($uets < $nbts)) || $nbts > $tend || $ci == $count); - $btsstart += (7 * 86400 * $interval); # addiere Tagesintervall, z.B. 4th Freitag ... - } - - } else { + $btsstart += (7 * 86400 * $interval); # addiere Tagesintervall, z.B. 4th Freitag ... + } + } + else { my ($nbhh,$nbmm,$nbss,$nehh,$nemm,$ness); my $rNewTime = $bts; for ($ci=-1; $ci<($count*$interval); $ci+=$interval) { - $rNewTime += $interval*604800 if($ci>=0); # Wochenintervall addieren + $rNewTime += $interval*604800 if($ci>=0); # Wochenintervall addieren ($nbss,$nbmm,$nbhh,$bmday,$bmonth,$byear,$ness,$nemm,$nehh,$emday,$emonth,$eyear) = DTfromStartandDiff ($rNewTime,$startEndDiff); $nbtime = $nbhh.$nbmm.$nbss; @@ -2192,9 +2059,9 @@ sub extractToDolist { ## no critic 'complexity' if(($ets && $ets < $tstart) || ($bts && $bts > $tend)) { Log3($name, 4, "$name - Ignore single task -> $data->{data}{$key}[$i]{summary} start: $bdate $btime, end: $edate $etime"); $ignore = 1; - $done = 0; - - } else { + $done = 0; + } + else { writeValuesToArray ({ name => $name, eventno => $n, calref => $data->{data}{$key}[$i], @@ -2259,7 +2126,7 @@ return createReadings ("$name|$rowlist"); # synchoner Mode ############################################################################################# # -# bewertet Zeitgrenzen und excludierende Parameter - ruft Schreiben der Daten in Ergenis +# bewertet Zeitgrenzen und excludierende Parameter - ruft Schreiben der Daten in Ergebnis # Array auf wenn Prüfung positiv # ############################################################################################# @@ -2297,19 +2164,19 @@ sub evalTimeAndWrite { if (defined $uets && ($uets < $nbts)) { # Event Ende (UNTIL) kleiner aktueller Select Start Log3($name, 4, "$name - Ignore ".$recurring." event due to UNTIL -> ".$calref->{summary}." , start: $nbdate $nbtime, end: $nedate $netime, until: $until"); $ignore = 1; - $done = 0; - - } elsif ($nets < $tstart || $nbts > $tend) { # Event Ende kleiner Select Start oder Beginn Event größer als Select Ende + $done = 0; + } + elsif ($nets < $tstart || $nbts > $tend) { # Event Ende kleiner Select Start oder Beginn Event größer als Select Ende Log3($name, 4, "$name - Ignore ".$recurring." event -> ".$calref->{summary}." , start: $nbdate $nbtime, end: $nedate $netime"); $ignore = 1; - $done = 0; - - } elsif ($excl) { + $done = 0; + } + elsif ($excl) { Log3($name, 4, "$name - ".$recurring." recurring event is deleted -> ".$calref->{summary}." , start: $nbdate $nbtime, end: $nedate $netime"); $ignore = 1; - $done = 0; - - } else { + $done = 0; + } + else { $bdate = $nbdate ? $nbdate : $bdate; $btime = $nbtime ? $nbtime : $btime; $bts = $nbts ? $nbts : $bts; @@ -2396,7 +2263,8 @@ sub writeValuesToArray { ## no (undef, undef, undef, undef, undef, undef, $bWday, undef, undef) = localtime($btimes); if($lang eq "DE") { @days = qw(Sontag Montag Dienstag Mittwoch Donnerstag Freitag Samstag); - } else { + } + else { @days = qw(Sunday Monday Tuesday Wednesday Thursday Friday Saturday); } $bWday = $days[$bWday]; @@ -2430,10 +2298,10 @@ sub writeValuesToArray { ## no push(@$aref, $bts+$n." 80_".sprintf("%0$l.0f", $ens)."_notifyDateTime " .$td."\n"); $alarmed = isAlarmed ($ts,$uts,$bts) if(!$alarmed); - - } elsif ($data{SSCal}{$name}{vcalendar}{"$uid"}{VALM}{RECURRENCEID}{$z} && + } + elsif ($data{SSCal}{$name}{vcalendar}{"$uid"}{VALM}{RECURRENCEID}{$z} && $data{SSCal}{$name}{vcalendar}{"$uid"}{VALM}{RECURRENCEID}{$z} eq $origdtstart) { - my ($y, $m, $d) = $bdate =~ /^(\d{4})-(\d{2})-(\d{2})/x; # Timestamp für Berechnung Erinnerungszeit Begindatum/Zeit ... + my ($y, $m, $d) = $bdate =~ /^(\d{4})-(\d{2})-(\d{2})/x; # Timestamp für Berechnung Erinnerungszeit Begindatum/Zeit ... my ($h, $mi, $s) = $btime =~ /^(\d{2}):(\d{2}):(\d{2})/x; eval { $chts = fhemTimeLocal($s, $mi, $h, $d, $m-1, $y-1900); # ... neu aus bdate/btime ableiten wegen Änderung durch Recurrance-id @@ -2457,7 +2325,7 @@ sub writeValuesToArray { ## no # restliche Keys extrahieren for my $p (keys %{$vh}) { $vh->{$p} = "" if(!defined $vh->{$p}); - $vh->{$p} = jboolmap($vh->{$p}); + $vh->{$p} = jboolmap ($vh->{$p}, "bin"); # boolsche Werte binär dekodieren next if($vh->{$p} eq ""); $val = encode("UTF-8", $vh->{$p}); @@ -2547,34 +2415,35 @@ sub createReadings { # Readings der Eventliste erstellen if($data{SSCal}{$name}{eventlist}) { - my $l = length(keys %{$data{SSCal}{$name}{eventlist}}); # Anzahl Stellen des max. Index ermitteln + my $l = length(keys %{$data{SSCal}{$name}{eventlist}}); # Anzahl Stellen des max. Index ermitteln readingsBeginUpdate($hash); - $data{SSCal}{$name}{lstUpdtTs} = $hash->{".updateTime"}; # letzte Updatezeit speichern (Unix Format) + $data{SSCal}{$name}{lstUpdtTs} = $hash->{".updateTime"}; # letzte Updatezeit speichern (Unix Format) my $k = 0; for my $idx (sort keys %{$data{SSCal}{$name}{eventlist}}) { - my $idxstr = sprintf("%0$l.0f", $k); # Blocknummer erstellen - push(@abnr, $idxstr); # Array aller vorhandener Blocknummern erstellen + my $idxstr = sprintf("%0$l.0f", $k); # Blocknummer erstellen + push(@abnr, $idxstr); # Array aller vorhandener Blocknummern erstellen for my $r (keys %{$data{SSCal}{$name}{eventlist}{$idx}}) { - if($r =~ /.*Timestamp$/x) { # Readings mit Unix Timestamps versteckt erstellen + if($r =~ /.*Timestamp$/x) { # Readings mit Unix Timestamps versteckt erstellen readingsBulkUpdate($hash, ".".$idxstr."_".$r, $data{SSCal}{$name}{eventlist}{$idx}{$r}); - } else { + } + else { readingsBulkUpdate($hash, $idxstr."_".$r, $data{SSCal}{$name}{eventlist}{$idx}{$r}); } } $k += 1; } - readingsEndUpdate($hash, 1); - - } else { - delReadings($name,0); # alle Kalender-Readings löschen + readingsEndUpdate($hash, 1); + } + else { + delReadings($name,0); # alle Kalender-Readings löschen } - doCompositeEvents ($name,\@abnr,$data{SSCal}{$name}{eventlist}); # spezifische Controlevents erstellen + doCompositeEvents ($name,\@abnr,$data{SSCal}{$name}{eventlist}); # spezifische Controlevents erstellen - checkRetry($name,0); + checkSendRetry ($name, 0, $queueStartFn); $data{SSCal}{$name}{lastUpdate} = FmtDateTime($data{SSCal}{$name}{lstUpdtTs}) if($data{SSCal}{$name}{lstUpdtTs}); @@ -2611,7 +2480,8 @@ sub doCompositeEvents { if(@{$abnr}) { my $nrs = join(" ", @{$abnr}); $event = "compositeBlockNumbers: $nrs"; - } else { + } + else { $event = "compositeBlockNumbers: none"; } CommandTrigger(undef, "$name $event"); @@ -2652,7 +2522,7 @@ sub createAtDevices { my $room = AttrVal($name, "room", ""); my $assoc = ""; - readingsDelete($hash,".associatedWith"); # Deviceassoziationen löschen + readingsDelete($hash,".associatedWith"); # Deviceassoziationen löschen @devs = devspec2array("TYPE=at:FILTER=NAME=SSCal.$name.*"); for (@devs) { @@ -2666,20 +2536,23 @@ sub createAtDevices { $desc = ReadingsVal($name, $bnr."_03_Description", ""); $begin = ReadingsVal($name, $bnr."_05_Begin", ""); $status = ReadingsVal($name, $bnr."_17_Status", ""); - $location = ReadingsVal($name, $bnr."_35_Location", $room); # Location wird als room gesetzt + $location = ReadingsVal($name, $bnr."_35_Location", $room); # Location wird als room gesetzt $id = ReadingsVal($name, $bnr."_98_EventId", ""); - if($begin && $status =~ /upcoming|alarmed/x && $desc =~ /^\s*\{(.*)\}\s*$/x) { # ein at-Device erstellen wenn Voraussetzungen erfüllt + if($begin && $status =~ /upcoming|alarmed/x && $desc =~ /^\s*\{(.*)\}\s*$/x) { # ein at-Device erstellen wenn Voraussetzungen erfüllt my $cmd = $1; - $begin =~ s/\s/T/x; # Formatierung nach ISO8601 (YYYY-MM-DDTHH:MM:SS) für at-Devices + $begin =~ s/\s/T/x; # Formatierung nach ISO8601 (YYYY-MM-DDTHH:MM:SS) für at-Devices my $ao = $begin; $ao =~ s/[-:]//gx; - my $atn = "SSCal.$name.$id.$ao"; # Name neues at-Device + my $atn = "SSCal.$name.$id.$ao"; # Name neues at-Device + Log3($name, 4, "$name - Command detected. Create device \"$atn\" with type \"at\"."); $err = CommandDefine(undef, "$atn at $begin $cmd"); + if ($err) { Log3($name, 1, "$name - Error during create \"$atn\": $err"); - } else { + } + else { CommandSetReading(undef, "$atn .associatedWith $name"); CommandAttr(undef,"$atn room $location"); CommandAttr(undef,"$atn alias $summary"); @@ -2704,7 +2577,7 @@ sub blockingTimeout { $cause = $cause?$cause:"Timeout: process terminated"; Log3 ($name, 1, "$name -> BlockingCall $hash->{HELPER}{RUNNING_PID}{fn} pid:$hash->{HELPER}{RUNNING_PID}{pid} $cause"); - checkRetry($name,0); + checkSendRetry ($name, 0, $queueStartFn); readingsBeginUpdate ($hash); readingsBulkUpdateIfChanged ($hash, "Error", $cause); @@ -2778,7 +2651,7 @@ return ($nbss,$nbmm,$nbhh,$bmday,$bmonth,$byear,$ness,$nemm,$nehh,$emday,$emonth # Auswertung mit $data{SSCal}{$name}{vcalendar}{"$uid"} # ############################################################################################# -sub extractIcal { ## no critic 'complexity' +sub extractIcal { ## no critic 'complexity' my ($name,$vh) = @_; my $hash = $defs{$name}; @@ -2795,7 +2668,8 @@ sub extractIcal { ## no critic 'complexity' for (@ical) { if($_ =~ m/^([-A-Z]*;)/x) { ($k,$v) = split(";", $_, 2); - } else { + } + else { ($k,$v) = split(":", $_, 2); } @@ -2898,7 +2772,8 @@ sub icalTimecheck { $min = substr($t,2,2); $sec = substr($t,4,2); $t = $hour.$min.$sec; - } else { + } + else { $hour = "00"; $min = "00"; $sec = "00"; @@ -2928,7 +2803,8 @@ sub isUpcoming { if($ats) { return $ts < $ats ? 1 : 0; - } else { + } + else { return $ts < $bts ? 1 : 0; } } @@ -2979,462 +2855,21 @@ return $ets <= $ts ? 1 : 0; # check SID ############################################################################################# sub checkSID { - my ($name) = @_; - my $hash = $defs{$name}; + my $name = shift; + my $hash = $defs{$name}; # SID holen bzw. login - my $subref = "calOp"; + my $subref = \&calOp; + if(!$hash->{HELPER}{SID}) { Log3($name, 3, "$name - no session ID found - get new one"); - login($hash,$subref); + login($hash, $data{SSCal}{$name}{calapi}, $subref, $name, $splitstr); return; } return calOp($name); } -#################################################################################### -# Login for SID -#################################################################################### -sub login { - my ($hash,$fret) = @_; - my $name = $hash->{NAME}; - my $serveraddr = $hash->{ADDR}; - my $serverport = $hash->{PORT}; - my $proto = $hash->{PROT}; - my $apiauth = $data{SSCal}{$name}{calapi}{APIAUTH}{NAME}; - my $apiauthpath = $data{SSCal}{$name}{calapi}{APIAUTH}{PATH}; - my $apiauthmaxver = $data{SSCal}{$name}{calapi}{APIAUTH}{MAX}; - - my $lrt = AttrVal($name,"loginRetries",3); - my ($url,$param); - - delete $hash->{HELPER}{SID}; - - # Login und SID ermitteln - Log3($name, 4, "$name - --- Start Synology Calendar login ---"); - - # Credentials abrufen - my ($success, $username, $password) = getcredentials($hash,0,"credentials"); - - unless ($success) { - Log3($name, 2, "$name - Credentials couldn't be obtained successfully - make sure you've set it with \"set $name credentials \""); - return; - } - - if($hash->{HELPER}{LOGINRETRIES} >= $lrt) { - # login wird abgebrochen - Log3($name, 2, "$name - ERROR - Login or privilege of user $username unsuccessful"); - return; - } - - my $timeout = AttrVal($name,"timeout",60); - $timeout = 120 if($timeout < 120); - Log3($name, 4, "$name - HTTP-Call login will be done with http timeout value: $timeout s"); - - my $urlwopw; # nur zur Anzeige bei verbose >= 4 und "showPassInLog" == 0 - - $url = "$proto://$serveraddr:$serverport/webapi/$apiauthpath?api=$apiauth&version=$apiauthmaxver&method=login&account=$username&passwd=$password&format=sid"; - $urlwopw = "$proto://$serveraddr:$serverport/webapi/$apiauthpath?api=$apiauth&version=$apiauthmaxver&method=login&account=$username&passwd=*****&format=sid"; - - AttrVal($name, "showPassInLog", "0") == 1 ? Log3($name, 4, "$name - Call-Out now: $url") : Log3($name, 4, "$name - Call-Out now: $urlwopw"); - $hash->{HELPER}{LOGINRETRIES}++; - - $param = { - url => $url, - timeout => $timeout, - hash => $hash, - user => $username, - funcret => $fret, - method => "GET", - header => "Accept: application/json", - callback => \&FHEM::SSCal::login_return - }; - HttpUtils_NonblockingGet ($param); - -return; -} - -sub login_return { - my ($param, $err, $myjson) = @_; - my $hash = $param->{hash}; - my $name = $hash->{NAME}; - my $username = $param->{user}; - my $fret = $param->{funcret}; - my $subref = \&$fret; - my $success; - - # Verarbeitung der asynchronen Rückkehrdaten aus sub "login_nonbl" - if ($err ne "") { - # ein Fehler bei der HTTP Abfrage ist aufgetreten - Log3($name, 2, "$name - error while requesting ".$param->{url}." - $err"); - - readingsSingleUpdate($hash, "Error", $err, 1); - - return login($hash,$fret); - - } elsif ($myjson ne "") { - # Evaluiere ob Daten im JSON-Format empfangen wurden - ($hash, $success) = evaljson($hash,$myjson); - unless ($success) { - Log3($name, 4, "$name - no JSON-Data returned while login: ".$myjson); - return; - } - - my $data = decode_json($myjson); - - # Logausgabe decodierte JSON Daten - Log3($name, 5, "$name - JSON decoded: ". Dumper $data); - - $success = $data->{'success'}; - - if ($success) { - # login war erfolgreich - my $sid = $data->{data}{sid}; - - # Session ID in hash eintragen - $hash->{HELPER}{SID} = $sid; - - readingsBeginUpdate ($hash); - readingsBulkUpdate ($hash,"Errorcode","none"); - readingsBulkUpdate ($hash,"Error","none"); - readingsEndUpdate ($hash, 1); - - Log3($name, 4, "$name - Login of User $username successful - SID: $sid"); - - return &$subref($name); - - } else { - # Errorcode aus JSON ermitteln - my $errorcode = $data->{error}{code}; - - # Fehlertext zum Errorcode ermitteln - my $error = experrorauth($hash,$errorcode); - - readingsBeginUpdate ($hash); - readingsBulkUpdate ($hash,"Errorcode", $errorcode); - readingsBulkUpdate ($hash,"Error", $error); - readingsBulkUpdate ($hash,"state", "error"); - readingsEndUpdate ($hash, 1); - - Log3($name, 3, "$name - Login of User $username unsuccessful. Code: $errorcode - $error - try again"); - - return login($hash,$fret); - } - } - -return login($hash,$fret); -} - -################################################################################### -# Funktion logout -################################################################################### -sub logout { - my ($hash) = @_; - my $name = $hash->{NAME}; - my $serveraddr = $hash->{ADDR}; - my $serverport = $hash->{PORT}; - my $proto = $hash->{PROT}; - my $apiauth = $data{SSCal}{$name}{calapi}{APIAUTH}{NAME}; - my $apiauthpath = $data{SSCal}{$name}{calapi}{APIAUTH}{PATH}; - my $apiauthmaxver = $data{SSCal}{$name}{calapi}{APIAUTH}{MAX}; - my $sid = $hash->{HELPER}{SID}; - my ($url,$param); - - Log3($name, 4, "$name - --- Start Synology Calendar logout ---"); - - my $timeout = AttrVal($name,"timeout",60); - $timeout = 60 if($timeout < 60); - Log3($name, 4, "$name - HTTP-Call logout will be done with http timeout value: $timeout s"); - - $url = "$proto://$serveraddr:$serverport/webapi/$apiauthpath?api=$apiauth&version=$apiauthmaxver&method=logout&_sid=$sid"; - - $param = { - url => $url, - timeout => $timeout, - hash => $hash, - method => "GET", - header => "Accept: application/json", - callback => \&FHEM::SSCal::logout_return - }; - - HttpUtils_NonblockingGet ($param); - -return; -} - -sub logout_return { - my ($param, $err, $myjson) = @_; - my $hash = $param->{hash}; - my $name = $hash->{NAME}; - my $sid = $hash->{HELPER}{SID}; - my $OpMode = $hash->{OPMODE}; - my ($success, $username, $password) = getcredentials($hash,0,"credentials"); - my ($data,$error,$errorcode); - - if ($err ne "") { - # wenn ein Fehler bei der HTTP Abfrage aufgetreten ist - Log3($name, 2, "$name - ERROR message: $err"); - - readingsBeginUpdate ($hash); - readingsBulkUpdateIfChanged ($hash, "Error", $err); - readingsBulkUpdateIfChanged ($hash, "Errorcode", "none"); - readingsBulkUpdate ($hash, "state", "Error"); - readingsEndUpdate ($hash,1); - - } elsif ($myjson ne "") { - # Evaluiere ob Daten im JSON-Format empfangen wurden - ($hash,$success,$myjson) = evaljson($hash,$myjson); - unless ($success) { - Log3($name, 4, "$name - Data returned: ".$myjson); - return; - } - - $data = decode_json($myjson); - - # Logausgabe decodierte JSON Daten - Log3($name, 5, "$name - JSON returned: ". Dumper $data); - - $success = $data->{'success'}; - - if ($success) { - # die Logout-URL konnte erfolgreich aufgerufen werden - Log3($name, 2, "$name - Session of User \"$username\" terminated - session ID \"$sid\" deleted"); - - } else { - # Errorcode aus JSON ermitteln - $errorcode = $data->{error}->{code}; - - # Fehlertext zum Errorcode ermitteln - $error = experrorauth($hash,$errorcode); - - Log3($name, 2, "$name - ERROR - Logout of User $username was not successful, however SID: \"$sid\" has been deleted. Errorcode: $errorcode - $error"); - } - } - - # Session-ID aus Helper-hash löschen - delete $hash->{HELPER}{SID}; - - CancelDelayedShutdown($name); - -return; -} - -############################################################################### -# Test ob JSON-String empfangen wurde -############################################################################### -sub evaljson { - my ($hash,$myjson) = @_; - my $OpMode = $hash->{OPMODE}; - my $name = $hash->{NAME}; - my $success = 1; - my ($error,$errorcode); - - eval {decode_json($myjson)} or do { - $success = 0; - - $errorcode = "900"; - - # Fehlertext zum Errorcode ermitteln - $error = experror($hash,$errorcode); - - readingsBeginUpdate ($hash); - readingsBulkUpdateIfChanged ($hash, "Errorcode", $errorcode); - readingsBulkUpdateIfChanged ($hash, "Error", $error); - readingsBulkUpdate ($hash, "state", "Error"); - readingsEndUpdate ($hash, 1); - }; - -return ($hash,$success,$myjson); -} - -############################################################################## -# Auflösung Errorcodes Calendar AUTH API -# Übernahmewerte sind $hash, $errorcode -############################################################################## -sub experrorauth { - my ($hash,$errorcode) = @_; - my $device = $hash->{NAME}; - my $error; - - unless (exists($errauthlist{"$errorcode"})) { - $error = "Value of errorcode \"$errorcode\" not found."; - return ($error); - } - - $error = $errauthlist{"$errorcode"}; - -return ($error); -} - -############################################################################## -# Auflösung Errorcodes Calendar API -# Übernahmewerte sind $hash, $errorcode -############################################################################## -sub experror { - my ($hash,$errorcode) = @_; - my $device = $hash->{NAME}; - my $error; - - unless (exists($errlist{"$errorcode"})) { - $error = "Value of errorcode \"$errorcode\" not found."; - return ($error); - } - - $error = $errlist{"$errorcode"}; - -return ($error); -} - -################################################################ -# sortiert eine Liste von Versionsnummern x.x.x -# Schwartzian Transform and the GRT transform -# Übergabe: "asc | desc", -################################################################ -sub sortVersion { - my ($sseq,@versions) = @_; - - return "" if(!@versions); - - my @sorted = map {$_->[0]} - sort {$a->[1] cmp $b->[1]} - map {[$_, pack "C*", split /\./x]} @versions; - - @sorted = map {join ".", unpack "C*", $_} - sort - map {pack "C*", split /\./x} @versions; - - if($sseq eq "desc") { - @sorted = reverse @sorted; - } - -return @sorted; -} - -###################################################################################### -# credentials speichern -###################################################################################### -sub setcredentials { - my ($hash, @credentials) = @_; - my $name = $hash->{NAME}; - my ($success, $credstr, $username, $passwd, $index, $retcode); - my (@key,$len,$i); - - my $ao = "credentials"; - $credstr = encode_base64(join('!_ESC_!', @credentials)); - - # Beginn Scramble-Routine - @key = qw(1 3 4 5 6 3 2 1 9); - $len = scalar @key; - $i = 0; - $credstr = join "", map { $i = ($i + 1) % $len; chr((ord($_) + $key[$i]) % 256) } split //, $credstr; - # End Scramble-Routine - - $index = $hash->{TYPE}."_".$hash->{NAME}."_".$ao; - $retcode = setKeyValue($index, $credstr); - - if ($retcode) { - Log3($name, 2, "$name - Error while saving Credentials - $retcode"); - $success = 0; - } else { - ($success, $username, $passwd) = getcredentials($hash,1,$ao); # Credentials nach Speicherung lesen und in RAM laden ($boot=1) - } - -return ($success); -} - -###################################################################################### -# credentials lesen -###################################################################################### -sub getcredentials { - my ($hash,$boot, $ao) = @_; - my $name = $hash->{NAME}; - my ($success, $username, $passwd, $index, $retcode, $credstr); - my (@key,$len,$i); - - if ($boot) { - # mit $boot=1 credentials von Platte lesen und als scrambled-String in RAM legen - $index = $hash->{TYPE}."_".$hash->{NAME}."_".$ao; - ($retcode, $credstr) = getKeyValue($index); - - if ($retcode) { - Log3($name, 2, "$name - Unable to read credentials from file: $retcode"); - $success = 0; - } - - if ($credstr) { - # beim Boot scrambled credentials in den RAM laden - $hash->{HELPER}{CREDENTIALS} = $credstr; - - # "CREDENTIALS" wird als Statusbit ausgewertet. Wenn nicht gesetzt -> Warnmeldung und keine weitere Verarbeitung - $hash->{CREDENTIALS} = "Set"; - $success = 1; - } - - } else { - # boot = 0 -> credentials aus RAM lesen, decoden und zurückgeben - $credstr = $hash->{HELPER}{CREDENTIALS}; - - if($credstr) { - # Beginn Descramble-Routine - @key = qw(1 3 4 5 6 3 2 1 9); - $len = scalar @key; - $i = 0; - $credstr = join "", - map { $i = ($i + 1) % $len; chr((ord($_) - $key[$i] + 256) % 256) } split //, $credstr; - # Ende Descramble-Routine - - ($username, $passwd) = split("!_ESC_!",decode_base64($credstr)); - - my $logcre = AttrVal($name, "showPassInLog", "0") == 1 ? $passwd : "********"; - - Log3($name, 4, "$name - credentials read from RAM: $username $logcre"); - - } else { - Log3($name, 2, "$name - credentials not set in RAM !"); - } - - $success = (defined($passwd)) ? 1 : 0; - } - -return ($success, $username, $passwd); -} - -############################################################################################# -# Leerzeichen am Anfang / Ende eines strings entfernen -############################################################################################# -sub trim { - my $str = shift; - - return if(!$str); - $str =~ s/^\s+|\s+$//gx; - -return ($str); -} - -############################################################################################# -# Länge Senedequeue updaten -############################################################################################# -sub updQLength { - my ($hash,$rst) = @_; - my $name = $hash->{NAME}; - - my $ql = keys %{$data{SSCal}{$name}{sendqueue}{entries}}; - - readingsBeginUpdate($hash); - readingsBulkUpdate ($hash, "QueueLength", $ql); # Länge Sendqueue updaten - readingsEndUpdate ($hash,1); - - my $head = "next planned SendQueue start:"; - if($rst) { # resend Timer gesetzt - $hash->{RESEND} = $head." ".FmtDateTime($rst); - } else { - $hash->{RESEND} = $head." immediately by next entry"; - } - -return; -} - ############################################################################################# # Start- und Endezeit ermitteln ############################################################################################# @@ -3455,7 +2890,8 @@ sub timeEdge { if($error) { Log3 $hash, 2, "$name: attribute cutOlderDays: $error"; return ($error,"",""); - } else { + } + else { $t1 = $t-$t1; (undef,undef,undef,$mday,$mon,$year,undef,undef,undef) = localtime($t1); # Istzeit Ableitung $t1 = fhemTimeLocal(00, 00, 00, $mday, $mon, $year); @@ -3466,7 +2902,8 @@ sub timeEdge { if($error) { Log3 $hash, 2, "$name: attribute cutLaterDays: $error"; return ($error,"",""); - } else { + } + else { $t2 = $t+$t2+$corr; (undef,undef,undef,$mday,$mon,$year,undef,undef,undef) = localtime($t2); # Istzeit Ableitung $t2 = fhemTimeLocal(00, 00, 00, $mday, $mon, $year); @@ -3498,17 +2935,23 @@ sub evtNotTime { if($tv =~ /^-P(\d)+D$/x) { $corr = -1*$1*86400; - } elsif ($tv =~ /^-PT(\d+)H$/x) { + } + elsif ($tv =~ /^-PT(\d+)H$/x) { $corr = -1*$1*3600; - } elsif ($tv =~ /^-PT(\d+)M$/x) { + } + elsif ($tv =~ /^-PT(\d+)M$/x) { $corr = -1*$1*60; - } elsif ($tv =~ /^PT(\d+)S$/x) { + } + elsif ($tv =~ /^PT(\d+)S$/x) { $corr = $1; - } elsif ($tv =~ /^PT(\d+)M$/x) { + } + elsif ($tv =~ /^PT(\d+)M$/x) { $corr = $1*60; - } elsif ($tv =~ /^PT(\d+)H$/x) { + } + elsif ($tv =~ /^PT(\d+)H$/x) { $corr = $1*3600; - } elsif ($tv =~ /^-P(\d)+DT(\d+)H$/x) { + } + elsif ($tv =~ /^-P(\d)+DT(\d+)H$/x) { $corr = -1*($1*86400 + $2*3600); } @@ -3554,57 +2997,6 @@ sub GetSecondsFromTimeSpec { return ("Wrong time specification $tspec", undef); } -############################################################################################# -# Clienthash übernehmen oder zusammenstellen -# Identifikation ob über FHEMWEB ausgelöst oder nicht -> erstellen $hash->CL -############################################################################################# -sub getclhash { - my ($hash,$nobgd) = @_; - my $name = $hash->{NAME}; - my $ret; - - if($nobgd) { - # nur übergebenen CL-Hash speichern, - # keine Hintergrundverarbeitung bzw. synthetische Erstellung CL-Hash - $hash->{HELPER}{CL}{1} = $hash->{CL}; - return; - } - - if (!defined($hash->{CL})) { - # Clienthash wurde nicht übergeben und wird erstellt (FHEMWEB Instanzen mit canAsyncOutput=1 analysiert) - my $outdev; - my @webdvs = devspec2array("TYPE=FHEMWEB:FILTER=canAsyncOutput=1:FILTER=STATE=Connected"); - my $i = 1; - for (@webdvs) { - $outdev = $_; - next if(!$defs{$outdev}); - $hash->{HELPER}{CL}{$i}->{NAME} = $defs{$outdev}{NAME}; - $hash->{HELPER}{CL}{$i}->{NR} = $defs{$outdev}{NR}; - $hash->{HELPER}{CL}{$i}->{COMP} = 1; - $i++; - } - } else { - # übergebenen CL-Hash in Helper eintragen - $hash->{HELPER}{CL}{1} = $hash->{CL}; - } - - # Clienthash auflösen zur Fehlersuche (aufrufende FHEMWEB Instanz - if (defined($hash->{HELPER}{CL}{1})) { - for (my $k=1; (defined($hash->{HELPER}{CL}{$k})); $k++ ) { - Log3($name, 4, "$name - Clienthash number: $k"); - while (my ($key,$val) = each(%{$hash->{HELPER}{CL}{$k}})) { - $val = $val?$val:" "; - Log3($name, 4, "$name - Clienthash: $key -> $val"); - } - } - } else { - Log3($name, 2, "$name - Clienthash was neither delivered nor created !"); - $ret = "Clienthash was neither delivered nor created. Can't use asynchronous output for function."; - } - -return ($ret); -} - ################################################################ # Kalendername aus Kalender-Id liefern ################################################################ @@ -3638,32 +3030,6 @@ sub plusNSeconds { return $t2; } -################################################################ -# alle Readings außer excludierte löschen -# $respts -> Respect Timestamp -# wenn gesetzt, wird Reading nicht gelöscht -# wenn Updatezeit identisch zu "lastUpdate" -################################################################ -sub delReadings { - my ($name,$respts) = @_; - my ($lu,$rts,$excl); - - $excl = "Error|Errorcode|QueueLength|state|nextUpdate"; - $excl .= "|lastUpdate" if($respts); - - my @allrds = keys%{$defs{$name}{READINGS}}; - for my $key(@allrds) { - if($respts) { - $lu = $data{SSCal}{$name}{lastUpdate}; - $rts = ReadingsTimestamp($name, $key, $lu); - next if($rts eq $lu); - } - delete($defs{$name}{READINGS}{$key}) if($key !~ m/^$excl$/x); - } - -return; -} - ############################################################################################# # Datum/Zeit extrahieren # Eingangsformat: TZID=Europe/Berlin:20191216T133000 oder @@ -3707,7 +3073,13 @@ sub explodeDateTime { ## no critic 'complexit $excl = 1 if($seen{$dtstart}); # check erfolgreich -> exclude recurring date weil (Serienelement gelöscht) # prüfen ob Serienelement verändert wurde - if($dt eq $dtstart) {$checkbegin = 1} else {$checkbegin = 0}; + if($dt eq $dtstart) { + $checkbegin = 1 + } + else { + $checkbegin = 0 + }; + if ($checkbegin) { # prüfen ob DTSTART verändert for (keys %{$data{SSCal}{$name}{vcalendar}{"$uid"}{RECURRENCEID}}) { @@ -3717,11 +3089,11 @@ sub explodeDateTime { ## no critic 'complexit if($z) { $changedt = $data{SSCal}{$name}{vcalendar}{"$uid"}{DTSTART}{$z}; my ($y, $m, $dd, $h, $mi, $s) = $changedt =~ /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})/x; - $changed = $y."-".$m."-".$dd; # einmalig geändertes Datum - $changet = $h.":".$mi.":".$s; # einmalig geänderte Zeit + $changed = $y."-".$m."-".$dd; # einmalig geändertes Datum + $changet = $h.":".$mi.":".$s; # einmalig geänderte Zeit } - } else { - # prüfen ob DTEND verändert + } + else { # prüfen ob DTEND verändert for (keys %{$data{SSCal}{$name}{vcalendar}{"$uid"}{RECURRENCEID}}) { next if(!$data{SSCal}{$name}{vcalendar}{"$uid"}{RECURRENCEID}{$_}); $z = $_ if($data{SSCal}{$name}{vcalendar}{"$uid"}{RECURRENCEID}{$_} eq $dtstart); @@ -3729,8 +3101,8 @@ sub explodeDateTime { ## no critic 'complexit if($z) { $changedt = $data{SSCal}{$name}{vcalendar}{"$uid"}{DTEND}{$z}; my ($y, $m, $dd, $h, $mi, $s) = $changedt =~ /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})/x; - $changed = $y."-".$m."-".$dd; # einmalig geändertes Datum - $changet = $h.":".$mi.":".$s; # einmalig geänderte Zeit + $changed = $y."-".$m."-".$dd; # einmalig geändertes Datum + $changet = $h.":".$mi.":".$s; # einmalig geänderte Zeit } } } @@ -3747,7 +3119,8 @@ sub explodeDateTime { ## no critic 'complexit $min = substr($t,2,2); $sec = substr($t,4,2); $t = $hour.":".$min.":".$sec; - } else { + } + else { $hour = "00"; $min = "00"; $sec = "00"; @@ -3790,54 +3163,6 @@ sub explodeDateTime { ## no critic 'complexit return ($invalid,$tz,$d,$t,$tstamp,$excl); } -############################################################################################# -# Versionierungen des Moduls setzen -# Die Verwendung von Meta.pm und Packages wird berücksichtigt -############################################################################################# -sub setVersionInfo { - my ($hash) = @_; - my $name = $hash->{NAME}; - - my $v = (sortTopicNum("desc",keys %vNotesIntern))[0]; - my $type = $hash->{TYPE}; - $hash->{HELPER}{PACKAGE} = __PACKAGE__; - $hash->{HELPER}{VERSION} = $v; - - if($modules{$type}{META}{x_prereqs_src} && !$hash->{HELPER}{MODMETAABSENT}) { - # META-Daten sind vorhanden - $modules{$type}{META}{version} = "v".$v; # Version aus META.json überschreiben, Anzeige mit {Dumper $modules{SSCal}{META}} - if($modules{$type}{META}{x_version}) { # {x_version} ( nur gesetzt wenn $Id$ im Kopf komplett! vorhanden ) - $modules{$type}{META}{x_version} =~ s/1\.1\.1/$v/gx; - } else { - $modules{$type}{META}{x_version} = $v; - } - return $@ unless (FHEM::Meta::SetInternals($hash)); # FVERSION wird gesetzt ( nur gesetzt wenn $Id$ im Kopf komplett! vorhanden ) - if(__PACKAGE__ eq "FHEM::$type" || __PACKAGE__ eq $type) { - # es wird mit Packages gearbeitet -> Perl übliche Modulversion setzen - # mit {->VERSION()} im FHEMWEB kann Modulversion abgefragt werden - use version 0.77; our $VERSION = FHEM::Meta::Get( $hash, 'version' ); ## no critic 'VERSION' - } - } else { - # herkömmliche Modulstruktur - $hash->{VERSION} = $v; - } - -return; -} - -############################################################################### -# JSON Boolean Test und Mapping -############################################################################### -sub jboolmap { - my ($bool) = @_; - - if(JSON::is_bool($bool)) { - $bool = $bool ? 1 : 0; - } - -return $bool; -} - ############################################################################################# # Kalenderliste als HTML-Tabelle zurückgeben ############################################################################################# @@ -3929,7 +3254,8 @@ sub calAsHtml { if ($small) { # nur ein Datumfeld umbrechbar $out .= " ".(($de)?'Start' :'Begin')." " if($seen{Begin}); $out .= " ".(($de)?'Ende' :'End')." " if($seen{End}); - } else { + } + else { $out .= " ".(($de)?'Start' :'Begin')." " if($seen{Begin}); $out .= " ".(($de)?'----' :'----')." " if($seen{Begin}); $out .= " ".(($de)?'Ende' :'End')." " if($seen{End}); @@ -4003,13 +3329,12 @@ sub calAsHtml { ); 1; } or do { Log3($name, 1, "$name - Syntax error in attribute \"tableSpecs\" near \"columnMapIcon\": $@") - }; - - } elsif ($mi eq "data") { - $micon = join(" ", split(",", $gpsc)); - - } elsif ($mi eq "text") { - # Karten-Text auswählen + }; + } + elsif ($mi eq "data") { + $micon = join(" ", split(",", $gpsc)); + } + elsif ($mi eq "text") { # Karten-Text auswählen my $dt = "link"; eval { $micon = evalTableSpecs ( { href => $hash, @@ -4022,9 +3347,9 @@ sub calAsHtml { ); 1; } or do { Log3($name, 1, "$name - Syntax error in attribute \"tableSpecs\" near \"columnMapText\": $@"); - }; - - } else { + }; + } + else { $micon = ""; } @@ -4049,13 +3374,14 @@ sub calAsHtml { if ($up eq "GoogleMaps") { # Kartenprovider: Google Maps $gps = " $micon "; - } elsif ($up eq "OpenStreetMap") { + } + elsif ($up eq "OpenStreetMap") { $gps = " $micon "; # Kartenprovider: OpenstreetMap } } - if($begin ne "") { # Datum sprachabhängig konvertieren bzw. heute/morgen setzen - my ($ny,$nm,$nd,undef) = split(/[\s-]/x, TimeNow()); # Jetzt + if($begin ne "") { # Datum sprachabhängig konvertieren bzw. heute/morgen setzen + my ($ny,$nm,$nd,undef) = split(/[\s-]/x, TimeNow()); # Jetzt my ($by,$bm,$bd,$bt) = split(/[\s-]/x, $begin); my ($ey,$em,$ed,$et) = split(/[\s-]/x, $end); my $ntimes = fhemTimeLocal(00, 00, 00, $nd, $nm-1, $ny-1900); @@ -4065,7 +3391,8 @@ sub calAsHtml { if($de) { $begind = "$bd.$bm.$by"; $endd = "$ed.$em.$ey"; - } else { + } + else { $begind = "$by-$bm-$bd"; $endd = "$ey-$em-$ed"; } @@ -4087,7 +3414,8 @@ sub calAsHtml { if (($begind eq $endd) && !$isallday) { $endd = ""; # bei "Ende" nur Uhrzeit angeben wenn Termin am gleichen Tag beginnt/endet aber kein Ganztagstermin ist - } elsif (($begind eq $endd) && $isallday) { + } + elsif (($begind eq $endd) && $isallday) { $begint = ""; $endt = ""; } @@ -4168,7 +3496,8 @@ sub calAsHtml { if($small) { $out .= " ".$begind." ".$begint. "" if($seen{Begin}); $out .= " ".$endd ." ".$endt. "" if($seen{End}); - } else { + } + else { $out .= " $begind " if($seen{Begin}); $out .= " $begint " if($seen{Begin}); $out .= " $endd " if($seen{End}); @@ -4262,7 +3591,8 @@ sub evalTableSpecs { ## if ( eval ($rval . $uval) ) { ## no critic 'eval' $ui = $specs->[$i]{icon}; $n--; - } else { + } + else { $ui = ""; } } @@ -4271,9 +3601,9 @@ sub evalTableSpecs { ## $default = $ui; # Defaultwert mit Select ersetzen wenn alle Bedingungen erfüllt } $i++; - } - - } elsif (ref($specs) eq "HASH") { # Wenn Schlüssel ein HASH enthält + } + } + elsif (ref($specs) eq "HASH") { # Wenn Schlüssel ein HASH enthält my $n = keys %{$specs}; # Anzahl Elemente (Entscheidungskriterien) in Hash for my $k (keys %{$specs}) { @@ -4296,7 +3626,8 @@ sub evalTableSpecs { ## if ( eval ($rval . $uval) ) { ## no critic 'eval' $ui = $specs->{icon}; $n--; - } else { + } + else { $ui = ""; } } @@ -4304,13 +3635,14 @@ sub evalTableSpecs { ## if($n == 0 && $ui) { $default = $ui; # Defaultwert mit Select ersetzen wenn alle Bedingungen erfüllt } - - } else { # ref Wert der Eigenschaft ist nicht HASH oder ARRAY + } + else { # ref Wert der Eigenschaft ist nicht HASH oder ARRAY if($specs =~ m/\{.*\}/xs) { # den Wert als Perl-Funktion ausführen wenn in {} $specs =~ s/\$NAME/$name/xg; # Platzhalter $NAME, $BNR ersetzen $specs =~ s/\$BNR/$bnr/xg; $default = $check->($specs); - } else { # einfache key-value Zuweisung + } + else { # einfache key-value Zuweisung eval ($default = $specs); ## no critic 'eval' } }