2020-01-19 14:21:41 +00:00
########################################################################################################################
# $Id: $
#########################################################################################################################
# 57_SSCal.pm
#
# (c) 2019 - 2020 by Heiko Maaz
# e-mail: Heiko dot Maaz at t-online dot de
#
# This Module integrate the Synology Calendar into FHEM
#
# This script is part of fhem.
#
# Fhem is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# Fhem is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with fhem. If not, see <http://www.gnu.org/licenses/>.
#
#########################################################################################################################
#
# Definition: define <name> SSCal <ServerAddr> [ServerPort] [Protocol]
#
# Example: define SynCal SSCal 192.168.2.20 [5000] [HTTP(S)]
#
package main ;
use strict ;
use warnings ;
eval "use JSON;1;" or my $ SSCalMM = "JSON" ; # Debian: apt-get install libjson-perl
use Data::Dumper ; # Perl Core module
use MIME::Base64 ;
use Time::HiRes ;
use HttpUtils ;
use Encode ;
use Blocking ;
no if $] >= 5.017011 , warnings = > 'experimental::smartmatch' ;
eval "use FHEM::Meta;1" or my $ modMetaAbsent = 1 ;
# no if $] >= 5.017011, warnings => 'experimental';
# Versions History intern
my % SSCal_vNotesIntern = (
2020-02-16 21:48:39 +00:00
"1.12.0" = > "15.02.2020 create At-devices from calendar entries if FHEM-commands or Perl-routines detected in \"Summary\" " ,
2020-02-14 13:20:59 +00:00
"1.11.0" = > "14.02.2020 new function SSCal_doCompositeEvents to create Composite Events for special notify use in FHEM " ,
2020-02-13 12:05:29 +00:00
"1.10.0" = > "13.02.2020 new key cellStyle for attribute tableSpecs, avoid FHEM crash when are design failures in tableSpecs " ,
2020-02-11 23:05:45 +00:00
"1.9.0" = > "11.02.2020 new reading Weekday with localization, more field selection for overview table " ,
2020-02-11 09:11:52 +00:00
"1.8.0" = > "09.02.2020 evaluate icons for DaysLeft, Map and State in sub SSCal_evalTableSpecs , fix no table is shown after FHEM restart " ,
2020-02-09 10:21:37 +00:00
"1.7.0" = > "09.02.2020 respect global language setting for some presentation, new attributes tableSpecs & tableColumnMap, days left in overview " .
"formatting overview table, feature smallScreen for tableSpecs, rename attributes to tableFields, " .
2020-02-09 15:05:13 +00:00
"tableInDetail, tableInRoom, correct enddate/time if is_all_day incl. bugfix API, function SSCal_boolean " .
"to avoid fhem crash if an older JSON module is installed " ,
2020-02-04 14:19:14 +00:00
"1.6.1" = > "03.02.2020 rename attributes to \"calOverviewInDetail\",\"calOverviewInRoom\", bugfix of gps extraction " ,
2020-02-08 09:18:56 +00:00
"1.6.0" = > "03.02.2020 new attribute \"tableFields\" to show specified fields in calendar overview in detail/room view, " .
2020-02-03 14:16:18 +00:00
"Model Diary/Tasks defined, periodic call of ToDo-Liists now possible " ,
2020-02-03 21:40:18 +00:00
"1.5.0" = > "02.02.2020 new attribute \"calOverviewInDetail\",\"calOverviewInRoom\" to control calendar overview in room or detail view " ,
2020-02-02 18:47:04 +00:00
"1.4.0" = > "02.02.2020 get calAsHtml command or use sub SSCal_calAsHtml(\$name) " ,
2020-02-01 19:54:01 +00:00
"1.3.1" = > "01.02.2020 add SSCal_errauthlist hash for login/logout API error codes " ,
2020-02-01 13:38:57 +00:00
"1.3.0" = > "01.02.2020 new command \"cleanCompleteTasks\" to delete completed tasks, \"deleteEventId\" to delete an event id, " .
"new get command \"apiInfo\" - detect and show API info, avoid empty readings " ,
2020-01-31 15:27:30 +00:00
"1.2.0" = > "29.01.2020 get tasks from calendar with set command 'calToDoList' " ,
"1.1.14" = > "29.01.2020 ignore calendars of type ne 'Event' for set calEventList " ,
2020-01-20 19:06:58 +00:00
"1.1.13" = > "20.01.2020 change save and read credentials routine " ,
2020-01-19 14:21:41 +00:00
"1.1.12" = > "19.01.2020 add attribute interval, automatic event fetch " ,
"1.1.11" = > "18.01.2020 status information added: upcoming, alarmed, started, ended " ,
"1.1.10" = > "17.01.2020 attribute asyncMode for parsing events in BlockingCall, some fixes " ,
"1.1.9" = > "14.01.2020 preparation of asynchronous calendar event extraction, some fixes " ,
"1.1.8" = > "13.01.2020 can proces WEEKLY general recurring events, use \$data{SSCal}{\$name}{eventlist} as Hash of Events " ,
"1.1.7" = > "12.01.2020 can proces WEEKLY recurring events BYDAY " ,
"1.1.6" = > "11.01.2020 can proces DAILY recurring events " ,
"1.1.5" = > "10.01.2020 can proces MONTHLY recurring events BYDAY " ,
"1.1.4" = > "07.01.2020 can proces MONTHLY recurring events BYMONTHDAY " ,
"1.1.3" = > "06.01.2020 can proces YEARLY recurring events " ,
"1.1.2" = > "04.01.2020 logout if new credentials are set " ,
"1.1.1" = > "03.01.2020 add array of 'evt_notify_setting' " ,
"1.1.0" = > "01.01.2020 logout command " ,
"1.0.0" = > "18.12.2019 initial "
) ;
# Versions History extern
my % SSCal_vNotesExtern = (
"1.0.0" = > "18.12.2019 initial "
) ;
2020-02-01 19:54:01 +00:00
# Aufbau Errorcode-Hashes
my % SSCal_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" ,
) ;
2020-01-19 14:21:41 +00:00
my % SSCal_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 und Forward-Deklaration
use vars qw( %SSCal_vHintsExt_en ) ;
use vars qw( %SSCal_vHintsExt_de ) ;
our % SSCal_api ;
################################################################
sub SSCal_Initialize ($) {
my ( $ hash ) = @ _ ;
2020-02-02 18:47:04 +00:00
$ hash - > { DefFn } = "SSCal_Define" ;
$ hash - > { UndefFn } = "SSCal_Undef" ;
$ hash - > { DeleteFn } = "SSCal_Delete" ;
$ hash - > { SetFn } = "SSCal_Set" ;
$ hash - > { GetFn } = "SSCal_Get" ;
$ hash - > { AttrFn } = "SSCal_Attr" ;
$ hash - > { DelayedShutdownFn } = "SSCal_DelayedShutdown" ;
2020-01-19 14:21:41 +00:00
2020-02-02 18:47:04 +00:00
# Darstellung FHEMWEB
# $hash->{FW_summaryFn} = "SSCal_FWsummaryFn";
$ hash - > { FW_addDetailToSummary } = 1 ; # zusaetzlich zu der Device-Summary auch eine Neue mit dem Inhalt von DetailFn angezeigt
$ hash - > { FW_detailFn } = "SSCal_FWdetailFn" ;
$ hash - > { FW_deviceOverview } = 1 ;
$ hash - > { AttrList } = "asyncMode:1,0 " .
2020-02-16 21:48:39 +00:00
"createATDevs:1,0 " .
2020-01-19 14:21:41 +00:00
"cutOlderDays " .
"cutLaterDays " .
"disable:1,0 " .
2020-02-07 14:59:41 +00:00
"tableSpecs:textField-long " .
2020-01-31 15:27:30 +00:00
"filterCompleteTask:1,2,3 " .
"filterDueTask:1,2,3 " .
2020-01-19 14:21:41 +00:00
"interval " .
2020-02-07 14:59:41 +00:00
"loginRetries:1,2,3,4,5,6,7,8,9,10 " .
"tableColumnMap:icon,data,text " .
2020-01-19 14:21:41 +00:00
"showRepeatEvent:true,false " .
"showPassInLog:1,0 " .
2020-02-08 09:18:56 +00:00
"tableInDetail:0,1 " .
"tableInRoom:0,1 " .
2020-02-12 16:28:05 +00:00
"tableFields:multiple-strict,Symbol,Begin,End,DaysLeft,DaysLeftLong,Weekday,Timezone,Summary,Description,Status,Completion,Location,Map,Calendar,EventId " .
2020-01-19 14:21:41 +00:00
"timeout " .
"usedCalendars:--wait#for#Calendar#list-- " .
$ readingFnAttributes ;
eval { FHEM::Meta:: InitMod ( __FILE__ , $ hash ) } ; # für Meta.pm (https://forum.fhem.de/index.php/topic,97589.0.html)
return ;
}
################################################################
# define SyncalBot SSCal 192.168.2.10 [5000] [HTTP(S)]
2020-02-01 13:38:57 +00:00
# [1] [2] [3] [4]
2020-01-19 14:21:41 +00:00
#
################################################################
sub SSCal_Define ($@) {
my ( $ hash , $ def ) = @ _ ;
my $ name = $ hash - > { NAME } ;
return "Error: Perl module " . $ SSCalMM . " is missing. Install it on Debian with: sudo apt-get install libjson-perl" if ( $ SSCalMM ) ;
my @ a = split ( "[ \t][ \t]*" , $ def ) ;
if ( int ( @ a ) < 2 ) {
2020-02-03 14:16:18 +00:00
return "You need to specify more parameters.\n" . "Format: define <name> SSCal <ServerAddress> [Port] [HTTP(S)] [Tasks]" ;
2020-01-19 14:21:41 +00:00
}
2020-02-03 14:16:18 +00:00
shift @ a ; shift @ a ;
my $ addr = $ a [ 0 ] if ( $ a [ 0 ] ne "Tasks" ) ;
my $ port = ( $ a [ 1 ] && $ a [ 1 ] ne "Tasks" ) ? $ a [ 1 ] : 5000 ;
my $ prot = ( $ a [ 2 ] && $ a [ 2 ] ne "Tasks" ) ? lc ( $ a [ 2 ] ) : "http" ;
my $ model = "Diary" ;
$ model = "Tasks" if ( grep { $ _ eq "Tasks" } @ a ) ;
2020-01-19 14:21:41 +00:00
$ hash - > { ADDR } = $ addr ;
$ hash - > { PORT } = $ port ;
$ hash - > { MODEL } = "Calendar" ;
$ hash - > { PROT } = $ prot ;
2020-02-03 14:16:18 +00:00
$ hash - > { MODEL } = $ model ;
2020-01-19 14:21:41 +00:00
$ 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
CommandAttr ( undef , "$name room SSCal" ) ;
2020-02-13 20:24:14 +00:00
CommandAttr ( undef , "$name event-on-update-reading .*Summary,state" ) ;
2020-01-19 14:21:41 +00:00
% SSCal_api = (
"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
) ;
# Versionsinformationen setzen
SSCal_setVersionInfo ( $ hash ) ;
# Credentials lesen
SSCal_getcredentials ( $ hash , 1 , "credentials" ) ;
# Index der Sendequeue initialisieren
$ data { SSCal } { $ name } { sendqueue } { index } = 0 ;
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdateIfChanged ( $ hash , "Errorcode" , "none" ) ;
readingsBulkUpdateIfChanged ( $ hash , "Error" , "none" ) ;
readingsBulkUpdateIfChanged ( $ hash , "QueueLenth" , 0 ) ; # Länge Sendqueue initialisieren
readingsBulkUpdate ( $ hash , "nextUpdate" , "Manual" ) ; # Abrufmode initial auf "Manual" setzen
readingsBulkUpdate ( $ hash , "state" , "Initialized" ) ; # Init state
readingsEndUpdate ( $ hash , 1 ) ;
# initiale Routinen nach Start ausführen , verzögerter zufälliger Start
SSCal_initonboot ( $ name ) ;
return undef ;
}
################################################################
# Die Undef-Funktion wird aufgerufen wenn ein Gerät mit delete
# gelöscht wird oder bei der Abarbeitung des Befehls rereadcfg,
# der ebenfalls alle Geräte löscht und danach das
# Konfigurationsfile neu einliest.
# Funktion: typische Aufräumarbeiten wie das
# saubere Schließen von Verbindungen oder das Entfernen von
# internen Timern, sofern diese im Modul zum Pollen verwendet
# wurden.
################################################################
sub SSCal_Undef ($$) {
my ( $ hash , $ arg ) = @ _ ;
my $ name = $ hash - > { NAME } ;
BlockingKill ( $ hash - > { HELPER } { RUNNING_PID } ) if ( $ hash - > { HELPER } { RUNNING_PID } ) ;
delete $ data { SSCal } { $ name } ;
RemoveInternalTimer ( $ name ) ;
return undef ;
}
#######################################################################################################
# Mit der X_DelayedShutdown Funktion kann eine Definition das Stoppen von FHEM verzögern um asynchron
# hinter sich aufzuräumen.
# Je nach Rückgabewert $delay_needed wird der Stopp von FHEM verzögert (0|1).
# Sobald alle nötigen Maßnahmen erledigt sind, muss der Abschluss mit CancelDelayedShutdown($name) an
# FHEM zurückgemeldet werden.
#######################################################################################################
sub SSCal_DelayedShutdown ($) {
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
if ( $ hash - > { HELPER } { SID } ) {
SSCal_logout ( $ hash ) ; # Session alter User beenden falls vorhanden
return 1 ;
}
return 0 ;
}
#################################################################
# Wenn ein Gerät in FHEM gelöscht wird, wird zuerst die Funktion
# X_Undef aufgerufen um offene Verbindungen zu schließen,
# anschließend wird die Funktion X_Delete aufgerufen.
# Funktion: Aufräumen von dauerhaften Daten, welche durch das
# Modul evtl. für dieses Gerät spezifisch erstellt worden sind.
# Es geht hier also eher darum, alle Spuren sowohl im laufenden
# FHEM-Prozess, als auch dauerhafte Daten bspw. im physikalischen
# Gerät zu löschen die mit dieser Gerätedefinition zu tun haben.
#################################################################
sub SSCal_Delete ($$) {
my ( $ hash , $ arg ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ index = $ hash - > { TYPE } . "_" . $ hash - > { NAME } . "_credentials" ;
# gespeicherte Credentials löschen
setKeyValue ( $ index , undef ) ;
return undef ;
}
################################################################
sub SSCal_Attr ($$$$) {
my ( $ cmd , $ name , $ aName , $ aVal ) = @ _ ;
2020-02-03 14:16:18 +00:00
my $ hash = $ defs { $ name } ;
my $ model = $ hash - > { MODEL } ;
2020-01-19 14:21:41 +00:00
my ( $ do , $ val , $ cache ) ;
# $cmd can be "del" or "set"
# $name is device name
# aName and aVal are Attribute name and value
2020-02-03 14:16:18 +00:00
if ( $ cmd eq "set" ) {
2020-02-05 23:12:14 +00:00
2020-02-03 14:16:18 +00:00
if ( $ aName =~ /filterCompleteTask|filterDueTask/ && $ model ne "Tasks" ) {
return " The attribute \"$aName\" is only valid for devices of MODEL \"Tasks\"! Please set this attribute in a device of this model." ;
}
if ( $ aName =~ /showRepeatEvent/ && $ model ne "Diary" ) {
return " The attribute \"$aName\" is only valid for devices of MODEL \"Diary\"! Please set this attribute in a device of this model." ;
}
2020-02-05 23:12:14 +00:00
2020-02-12 21:12:47 +00:00
if ( $ aName =~ /tableSpecs/ ) {
return " The attribute \"$aName\" has wrong syntax. The value must be set into \"{ }\". " if ( $ aVal !~ m/^\s*\{.*\}\s*$/s ) ;
}
my $ attrVal = $ aVal ;
2020-02-11 09:11:52 +00:00
if ( $ attrVal =~ m/^\{.*\}$/s && $ attrVal =~ m/=>/ ) {
$ attrVal =~ s/\@/\\\@/g ;
$ attrVal =~ s/\$/\\\$/g ;
2020-02-05 23:12:14 +00:00
my $ av = eval $ attrVal ;
if ( $@ ) {
Log3 ( $ name , 2 , "$name - Error while evaluate: " . $@ ) ;
2020-02-08 09:18:56 +00:00
return $@ ;
2020-02-05 23:12:14 +00:00
} else {
$ attrVal = $ av if ( ref ( $ av ) eq "HASH" ) ;
}
}
$ hash - > { HELPER } { $ aName } = $ attrVal ;
} else {
delete $ hash - > { HELPER } { $ aName } ;
}
2020-01-19 14:21:41 +00:00
if ( $ aName eq "disable" ) {
if ( $ cmd eq "set" ) {
$ do = $ aVal ? 1 : 0 ;
}
$ do = 0 if ( $ cmd eq "del" ) ;
$ val = ( $ do == 1 ? "disabled" : "initialized" ) ;
if ( $ do == 1 ) {
RemoveInternalTimer ( $ name ) ;
} else {
InternalTimer ( gettimeofday ( ) + 2 , "SSCal_initonboot" , $ name , 0 ) if ( $ init_done ) ;
}
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdate ( $ hash , "state" , $ val ) ;
readingsEndUpdate ( $ hash , 1 ) ;
}
if ( $ cmd eq "set" ) {
if ( $ aName =~ m/timeout|cutLaterDays|cutOlderDays|interval/ ) {
unless ( $ aVal =~ /^\d+$/ ) { return "The Value for $aName is not valid. Use only figures 1-9 !" ; }
}
if ( $ aName =~ m/interval/ ) {
RemoveInternalTimer ( $ name , "SSCal_periodicCall" ) ;
if ( $ aVal > 0 ) {
InternalTimer ( gettimeofday ( ) + 1.0 , "SSCal_periodicCall" , $ name , 0 ) ;
}
}
}
return undef ;
}
################################################################
sub SSCal_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 ] ;
2020-02-03 14:16:18 +00:00
my $ model = $ hash - > { MODEL } ;
2020-01-19 14:21:41 +00:00
my ( $ success , $ setlist ) ;
return if ( IsDisabled ( $ name ) ) ;
2020-02-16 21:48:39 +00:00
my $ idxlist = join ( "," , sort keys % { $ data { SSCal } { $ name } { sendqueue } { entries } } ) ;
2020-02-01 13:38:57 +00:00
# alle aktuell angezeigten Event Id's ermitteln
my ( @ idarray , $ evids ) ;
foreach my $ key ( keys % { $ defs { $ name } { READINGS } } ) {
next if $ key !~ /^.*_EventId$/ ;
push ( @ idarray , $ defs { $ name } { READINGS } { $ key } { VAL } ) ;
}
if ( @ idarray ) {
my % seen ;
my @ unique = sort { $ a <=> $ b } grep { ! $ seen { $ _ } + + } @ idarray ; # distinct / unique the keys
$ evids = join ( "," , @ unique ) ;
}
2020-01-19 14:21:41 +00:00
if ( ! $ hash - > { CREDENTIALS } ) {
# initiale setlist für neue Devices
$ setlist = "Unknown argument $opt, choose one of " .
"credentials "
;
2020-02-03 14:16:18 +00:00
} elsif ( $ model eq "Diary" ) {
2020-01-19 14:21:41 +00:00
$ setlist = "Unknown argument $opt, choose one of " .
"calEventList " .
2020-02-03 14:16:18 +00:00
"credentials " .
( $ evids ? "deleteEventId:$evids " : "deleteEventId:noArg " ) .
"eraseReadings:noArg " .
"listSendqueue:noArg " .
"logout:noArg " .
( $ idxlist ? "purgeSendqueue:-all-,-permError-,$idxlist " : "purgeSendqueue:-all-,-permError- " ) .
"restartSendqueue:noArg "
;
} else { # Model ist "Tasks"
$ setlist = "Unknown argument $opt, choose one of " .
2020-01-31 15:27:30 +00:00
"calToDoList " .
2020-02-01 13:38:57 +00:00
"cleanCompleteTasks:noArg " .
2020-01-19 14:21:41 +00:00
"credentials " .
2020-02-01 13:38:57 +00:00
( $ evids ? "deleteEventId:$evids " : "deleteEventId:noArg " ) .
2020-01-19 14:21:41 +00:00
"eraseReadings:noArg " .
"listSendqueue:noArg " .
"logout:noArg " .
( $ idxlist ? "purgeSendqueue:-all-,-permError-,$idxlist " : "purgeSendqueue:-all-,-permError- " ) .
"restartSendqueue:noArg "
;
}
if ( $ opt eq "credentials" ) {
return "The command \"$opt\" needs an argument." if ( ! $ prop ) ;
SSCal_logout ( $ hash ) if ( $ hash - > { HELPER } { SID } ) ; # Session alter User beenden falls vorhanden
( $ success ) = SSCal_setcredentials ( $ hash , $ prop , $ prop1 ) ;
if ( $ success ) {
SSCal_addQueue ( $ name , "listcal" , "CALCAL" , "list" , "&is_todo=true&is_evt=true" ) ;
SSCal_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 ;
foreach 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 ;
foreach my $ idx ( sort { $ a <=> $ b } keys % { $ data { SSCal } { $ name } { sendqueue } { entries } } ) {
$ sq . = $ idx . " => " . $ sub - > ( $ idx ) . "\n" ;
}
return $ sq ;
} 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-" ) {
foreach 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" ;
}
} elsif ( $ opt eq "calEventList" ) { # Termine einer Cal_id (Liste) in Zeitgrenzen abrufen
return "Obtain the Calendar list first with \"get $name getCalendars\" command." if ( ! $ hash - > { HELPER } { CALFETCHED } ) ;
my ( $ err , $ tstart , $ tend ) = SSCal_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 $ 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 ;
foreach ( @ ca ) {
my $ oid = $ hash - > { HELPER } { CALENDARS } { "$_" } { id } ;
2020-01-29 18:49:09 +00:00
next if ( ! $ oid ) ;
if ( $ hash - > { HELPER } { CALENDARS } { "$_" } { type } ne "Event" ) {
2020-01-31 15:27:30 +00:00
Log3 ( $ name , 3 , "$name - The Calendar \"$_\" is not of type \"Event\" and will be ignored." ) ;
2020-01-29 18:49:09 +00:00
next ;
}
2020-01-19 14:21:41 +00:00
$ oids . = "," if ( $ oids ) ;
$ oids . = '"' . $ oid . '"' ;
Log3 ( $ name , 2 , "$name - WARNING - The Calendar \"$_\" seems to be unknown because its ID couldn't be found." ) if ( ! $ oid ) ;
}
2020-01-31 15:27:30 +00:00
return "No Calendar of type \"Event\" was selected or its ID(s) couldn't be found." if ( ! $ oids ) ;
2020-01-19 14:21:41 +00:00
Log3 ( $ name , 5 , "$name - Calendar selection for add queue: $cals" ) ;
my $ lr = AttrVal ( $ name , "showRepeatEvent" , "true" ) ;
SSCal_addQueue ( $ name , "eventlist" , "CALEVENT" , "list" , "&cal_id_list=[$oids]&start=$tstart&end=$tend&list_repeat=$lr" ) ;
SSCal_getapisites ( $ name ) ;
2020-01-31 15:27:30 +00:00
} elsif ( $ opt eq "calToDoList" ) { # Aufgaben einer Cal_id (Liste) 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 ;
foreach ( @ ca ) {
my $ oid = $ hash - > { HELPER } { CALENDARS } { "$_" } { id } ;
next if ( ! $ oid ) ;
if ( $ hash - > { HELPER } { CALENDARS } { "$_" } { type } ne "ToDo" ) {
Log3 ( $ name , 3 , "$name - The Calendar \"$_\" is not of type \"ToDo\" and will be ignored." ) ;
next ;
}
$ oids . = "," if ( $ oids ) ;
$ oids . = '"' . $ oid . '"' ;
Log3 ( $ name , 2 , "$name - WARNING - The Calendar \"$_\" 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" ) ;
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
SSCal_addQueue ( $ name , "todolist" , "CALTODO" , "list" , "&cal_id_list=[$oids]&limit=$limit&offset=$offset&filter_due=$filterdue&filter_complete=$filtercomplete" ) ;
SSCal_getapisites ( $ name ) ;
2020-02-01 13:38:57 +00:00
} 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 ;
foreach ( @ ca ) {
my $ oid = $ hash - > { HELPER } { CALENDARS } { "$_" } { id } ;
next if ( ! $ oid ) ;
if ( $ hash - > { HELPER } { CALENDARS } { "$_" } { type } ne "ToDo" ) {
Log3 ( $ name , 3 , "$name - The Calendar \"$_\" is not of type \"ToDo\" and will be ignored." ) ;
next ;
}
$ oids . = "," if ( $ oids ) ;
$ oids . = '"' . $ oid . '"' ;
Log3 ( $ name , 2 , "$name - WARNING - The Calendar \"$_\" 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" ) ;
# <Name, operation mode, API (siehe %SSCal_api), auszuführende API-Methode, spezifische API-Parameter>
SSCal_addQueue ( $ name , "cleanCompleteTasks" , "CALTODO" , "clean_complete" , "&cal_id_list=[$oids]" ) ;
SSCal_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 } } ;
foreach my $ key ( @ allrds ) {
next if $ key !~ /^.*_EventId$/ ;
$ 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 ( ! $ 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\"." ) ;
# <Name, operation mode, API (siehe %SSCal_api), auszuführende API-Methode, spezifische API-Parameter>
SSCal_addQueue ( $ name , "deleteEventId" , $ api , "delete" , "&evt_id=$eventid" ) ;
SSCal_getapisites ( $ name ) ;
2020-01-19 14:21:41 +00:00
} elsif ( $ opt eq "restartSendqueue" ) {
my $ ret = SSCal_getapisites ( $ name ) ;
if ( $ ret ) {
return $ ret ;
} else {
return "The SendQueue has been restarted." ;
}
} elsif ( $ opt eq 'eraseReadings' ) {
SSCal_delReadings ( $ name , 0 ) ; # Readings löschen
} elsif ( $ opt eq 'logout' ) {
SSCal_logout ( $ hash ) ;
} else {
return "$setlist" ;
}
return ;
}
################################################################
sub SSCal_Get ($@) {
my ( $ hash , @ a ) = @ _ ;
return "\"get X\" needs at least an argument" if ( @ a < 2 ) ;
my $ name = shift @ a ;
my $ opt = shift @ a ;
my $ arg = shift @ a ;
my $ arg1 = shift @ a ;
my $ arg2 = shift @ a ;
my $ ret = "" ;
my $ getlist ;
if ( ! $ hash - > { CREDENTIALS } ) {
return ;
} else {
$ getlist = "Unknown argument $opt, choose one of " .
2020-02-01 13:38:57 +00:00
"apiInfo:noArg " .
2020-02-02 18:47:04 +00:00
"calAsHtml:noArg " .
2020-01-19 14:21:41 +00:00
"getCalendars:noArg " .
"storedCredentials:noArg " .
"versionNotes "
;
}
return if ( IsDisabled ( $ name ) ) ;
if ( $ opt eq "storedCredentials" ) {
if ( ! $ hash - > { CREDENTIALS } ) { return "Credentials of $name are not set - make sure you've set it with \"set $name credentials <CREDENTIALS>\"" ; }
# Credentials abrufen
my ( $ success , $ username , $ passwd ) = SSCal_getcredentials ( $ hash , 0 , "credentials" ) ;
unless ( $ success ) { return "Credentials couldn't be retrieved successfully - see logfile" } ;
return "Stored Credentials:\n" .
"===================\n" .
"Username: $username, Password: $passwd \n"
;
2020-02-01 13:38:57 +00:00
} elsif ( $ opt eq "apiInfo" ) { # Liste aller Kalender abrufen
# übergebenen CL-Hash (FHEMWEB) in Helper eintragen
SSCal_getclhash ( $ hash , 1 ) ;
$ hash - > { HELPER } { APIPARSET } = 0 ; # Abruf API Infos erzwingen
# <Name, operation mode, API (siehe %SSCal_api), auszuführende API-Methode, spezifische API-Parameter>
SSCal_addQueue ( $ name , "apiInfo" , "" , "" , "" ) ;
SSCal_getapisites ( $ name ) ;
} elsif ( $ opt eq "getCalendars" ) { # Liste aller Kalender abrufen
2020-01-19 14:21:41 +00:00
# übergebenen CL-Hash (FHEMWEB) in Helper eintragen
2020-01-20 06:29:27 +00:00
SSCal_getclhash ( $ hash , 1 ) ;
2020-01-19 14:21:41 +00:00
SSCal_addQueue ( $ name , "listcal" , "CALCAL" , "list" , "&is_todo=true&is_evt=true" ) ;
SSCal_getapisites ( $ name ) ;
2020-02-02 18:47:04 +00:00
} elsif ( $ opt eq "calAsHtml" ) {
my $ out = SSCal_calAsHtml ( $ name ) ;
return $ out ;
2020-01-19 14:21:41 +00:00
} elsif ( $ opt =~ /versionNotes/ ) {
my $ header = "<b>Module release information</b><br>" ;
my $ header1 = "<b>Helpful hints</b><br>" ;
my % hs ;
# Ausgabetabelle erstellen
my ( $ ret , $ val0 , $ val1 ) ;
my $ i = 0 ;
$ ret = "<html>" ;
# Hints
if ( ! $ arg || $ arg =~ /hints/ || $ arg =~ /[\d]+/ ) {
$ ret . = sprintf ( "<div class=\"makeTable wide\"; style=\"text-align:left\">$header1 <br>" ) ;
$ ret . = "<table class=\"block wide internals\">" ;
$ ret . = "<tbody>" ;
$ ret . = "<tr class=\"even\">" ;
if ( $ arg && $ arg =~ /[\d]+/ ) {
my @ hints = split ( "," , $ arg ) ;
foreach ( @ hints ) {
if ( AttrVal ( "global" , "language" , "EN" ) eq "DE" ) {
$ hs { $ _ } = $ SSCal_vHintsExt_de { $ _ } ;
} else {
$ hs { $ _ } = $ SSCal_vHintsExt_en { $ _ } ;
}
}
} else {
if ( AttrVal ( "global" , "language" , "EN" ) eq "DE" ) {
% hs = % SSCal_vHintsExt_de ;
} else {
% hs = % SSCal_vHintsExt_en ;
}
}
$ i = 0 ;
foreach my $ key ( SSCal_sortVersion ( "desc" , keys % hs ) ) {
$ val0 = $ hs { $ key } ;
$ ret . = sprintf ( "<td style=\"vertical-align:top\"><b>$key</b> </td><td style=\"vertical-align:top\">$val0</td>" ) ;
$ ret . = "</tr>" ;
$ i + + ;
if ( $ i & 1 ) {
# $i ist ungerade
$ ret . = "<tr class=\"odd\">" ;
} else {
$ ret . = "<tr class=\"even\">" ;
}
}
$ ret . = "</tr>" ;
$ ret . = "</tbody>" ;
$ ret . = "</table>" ;
$ ret . = "</div>" ;
}
# Notes
if ( ! $ arg || $ arg =~ /rel/ ) {
$ ret . = sprintf ( "<div class=\"makeTable wide\"; style=\"text-align:left\">$header <br>" ) ;
$ ret . = "<table class=\"block wide internals\">" ;
$ ret . = "<tbody>" ;
$ ret . = "<tr class=\"even\">" ;
$ i = 0 ;
foreach my $ key ( SSCal_sortVersion ( "desc" , keys % SSCal_vNotesExtern ) ) {
( $ val0 , $ val1 ) = split ( /\s/ , $ SSCal_vNotesExtern { $ key } , 2 ) ;
$ ret . = sprintf ( "<td style=\"vertical-align:top\"><b>$key</b> </td><td style=\"vertical-align:top\">$val0 </td><td>$val1</td>" ) ;
$ ret . = "</tr>" ;
$ i + + ;
if ( $ i & 1 ) {
# $i ist ungerade
$ ret . = "<tr class=\"odd\">" ;
} else {
$ ret . = "<tr class=\"even\">" ;
}
}
$ ret . = "</tr>" ;
$ ret . = "</tbody>" ;
$ ret . = "</table>" ;
$ ret . = "</div>" ;
}
$ ret . = "</html>" ;
return $ ret ;
} else {
return "$getlist" ;
}
return $ ret ; # not generate trigger out of command
}
2020-02-02 18:47:04 +00:00
######################################################################################
# Kalenderübersicht in Detailanzeige darstellen
######################################################################################
sub SSCal_FWdetailFn ($$$$) {
my ( $ FW_wname , $ d , $ room , $ pageHash ) = @ _ ;
my $ hash = $ defs { $ d } ;
my $ ret = "" ;
2020-02-08 11:45:46 +00:00
$ hash - > { ".calhtml" } = SSCal_calAsHtml ( $ d , $ FW_wname ) ;
2020-02-02 18:47:04 +00:00
2020-02-08 09:18:56 +00:00
if ( $ hash - > { ".calhtml" } ne "" && ! $ room && AttrVal ( $ d , "tableInDetail" , 1 ) ) { # Anzeige Übersicht in Detailansicht
2020-02-02 18:47:04 +00:00
$ ret . = $ hash - > { ".calhtml" } ;
return $ ret ;
}
2020-02-08 09:18:56 +00:00
if ( $ hash - > { ".calhtml" } ne "" && $ room && AttrVal ( $ d , "tableInRoom" , 1 ) ) { # Anzeige in Raumansicht zusätzlich zur Statuszeile
2020-02-02 18:47:04 +00:00
$ ret = $ hash - > { ".calhtml" } ;
return $ ret ;
}
return undef ;
}
2020-01-19 14:21:41 +00:00
######################################################################################
# initiale Startroutinen nach Restart FHEM
######################################################################################
sub SSCal_initonboot ($) {
my ( $ name ) = @ _ ;
my $ hash = $ defs { $ name } ;
my ( $ ret ) ;
RemoveInternalTimer ( $ name , "SSCal_initonboot" ) ;
if ( $ init_done ) {
CommandGet ( undef , "$name getCalendars" ) ; # Kalender Liste initial abrufen
} else {
InternalTimer ( gettimeofday ( ) + 3 , "SSCal_initonboot" , $ name , 0 ) ;
}
return ;
}
#############################################################################################
# regelmäßiger Intervallabruf
#############################################################################################
sub SSCal_periodicCall ($) {
my ( $ name ) = @ _ ;
my $ hash = $ defs { $ name } ;
my $ interval = AttrVal ( $ name , "interval" , 0 ) ;
2020-02-03 14:16:18 +00:00
my $ model = $ hash - > { MODEL } ;
2020-01-19 14:21:41 +00:00
my $ new ;
if ( ! $ interval ) {
$ hash - > { MODE } = "Manual" ;
} else {
$ new = gettimeofday ( ) + $ interval ;
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdate ( $ hash , "nextUpdate" , "Automatic - next polltime: " . FmtTime ( $ new ) ) ; # Abrufmode initial auf "Manual" setzen
readingsEndUpdate ( $ hash , 1 ) ;
}
RemoveInternalTimer ( $ name , "SSCal_periodicCall" ) ;
return if ( ! $ interval ) ;
if ( $ hash - > { CREDENTIALS } && ! IsDisabled ( $ name ) ) {
2020-02-03 14:16:18 +00:00
if ( $ model eq "Diary" ) { CommandSet ( undef , "$name calEventList" ) } ; # Einträge aller gewählter Terminkalender abrufen (in Queue stellen)
if ( $ model eq "Tasks" ) { CommandSet ( undef , "$name calToDoList" ) } ; # Einträge aller gewählter Aufgabenlisten abrufen (in Queue stellen)
2020-01-19 14:21:41 +00:00
}
InternalTimer ( $ new , "SSCal_periodicCall" , $ name , 0 ) ;
return ;
}
######################################################################################
# Eintrag zur SendQueue hinzufügen
2020-01-31 15:27:30 +00:00
# $name = Name Kalenderdevice
# $opmode = operation mode
# $api = API (siehe %SSCal_api)
# $method = auszuführende API-Methode
# $params = spezifische API-Parameter
#
2020-01-19 14:21:41 +00:00
######################################################################################
sub SSCal_addQueue ($$$$$) {
my ( $ name , $ opmode , $ api , $ method , $ params ) = @ _ ;
my $ hash = $ defs { $ name } ;
$ data { SSCal } { $ name } { sendqueue } { index } + + ;
my $ index = $ data { SSCal } { $ name } { sendqueue } { index } ;
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
} ;
$ data { SSCal } { $ name } { sendqueue } { entries } { $ index } = $ pars ;
SSCal_updQLength ( $ hash ) ; # 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 SSCal_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 ..." ) ;
SSCal_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." ) ;
SSCal_updQLength ( $ hash ) ;
return SSCal_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/ ) {
delete $ hash - > { HELPER } { SID } ;
}
2020-02-01 13:38:57 +00:00
if ( $ errorcode =~ /100|101|103|117|120|407|409|800|900/ ) { # bei diesen Errorcodes den Queueeintrag nicht wiederholen, da dauerhafter Fehler !
$ forbidSend = SSCal_experror ( $ hash , $ errorcode ) ; # Fehlertext zum Errorcode ermitteln
2020-01-19 14:21:41 +00:00
$ 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 } ;
SSCal_updQLength ( $ hash ) ; # updaten Länge der Sendequeue
return SSCal_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
SSCal_updQLength ( $ hash , $ rst ) ; # updaten Länge der Sendequeue mit resend Timer
RemoveInternalTimer ( $ name , "SSCal_getapisites" ) ;
InternalTimer ( $ rst , "SSCal_getapisites" , "$name" , 0 ) ;
}
}
return
}
#############################################################################################################################
####### Begin Kameraoperationen mit NonblockingGet (nicht blockierender HTTP-Call) #######
#############################################################################################################################
sub SSCal_getapisites ($) {
my ( $ name ) = @ _ ;
my $ hash = $ defs { $ name } ;
my $ addr = $ hash - > { ADDR } ;
my $ port = $ hash - > { PORT } ;
my $ prot = $ hash - > { PROT } ;
my ( $ url , $ param , $ idxset , $ ret ) ;
$ hash - > { HELPER } { LOGINRETRIES } = 0 ;
my ( $ err , $ tstart , $ tend ) = SSCal_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 - ####################################################" ) ;
if ( ! keys % { $ data { SSCal } { $ name } { sendqueue } { entries } } ) {
$ ret = "Sendqueue is empty. Nothing to do ..." ;
Log3 ( $ name , 4 , "$name - $ret" ) ;
return $ ret ;
}
# den nächsten Eintrag aus "SendQueue" selektieren und ausführen wenn nicht forbidSend gesetzt ist
foreach my $ idx ( sort { $ a <=> $ b } keys % { $ data { SSCal } { $ name } { sendqueue } { entries } } ) {
if ( ! $ data { SSCal } { $ name } { sendqueue } { entries } { $ idx } { forbidSend } ) {
$ hash - > { OPIDX } = $ idx ;
$ hash - > { OPMODE } = $ data { SSCal } { $ name } { sendqueue } { entries } { $ idx } { opmode } ;
$ idxset = 1 ;
last ;
}
}
if ( ! $ idxset ) {
$ ret = "Only entries with \"forbidSend\" are in Sendqueue. Escaping ..." ;
Log3 ( $ name , 4 , "$name - $ret" ) ;
return $ ret ;
}
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
Log3 ( $ name , 4 , "$name - API hash values already set - ignore get apisites" ) ;
return SSCal_checkSID ( $ name ) ;
}
my $ timeout = AttrVal ( $ name , "timeout" , 20 ) ;
Log3 ( $ name , 5 , "$name - HTTP-Call will be done with timeout: $timeout s" ) ;
# URL zur Abfrage der Eigenschaften der API's
2020-02-01 13:38:57 +00:00
$ url = "$prot://$addr:$port/webapi/query.cgi?api=$SSCal_api{APIINFO}{NAME}&method=Query&version=1&query=$SSCal_api{APIAUTH}{NAME},$SSCal_api{CALCAL}{NAME},$SSCal_api{CALEVENT}{NAME},$SSCal_api{CALSHARE}{NAME},$SSCal_api{CALTODO}{NAME},$SSCal_api{APIINFO}{NAME}" ;
2020-01-19 14:21:41 +00:00
Log3 ( $ name , 4 , "$name - Call-Out: $url" ) ;
$ param = {
url = > $ url ,
timeout = > $ timeout ,
hash = > $ hash ,
method = > "GET" ,
header = > "Accept: application/json" ,
callback = > \ & SSCal_getapisites_parse
} ;
HttpUtils_NonblockingGet ( $ param ) ;
return ;
}
####################################################################################
# Auswertung Abruf apisites
####################################################################################
sub SSCal_getapisites_parse ($) {
my ( $ param , $ err , $ myjson ) = @ _ ;
2020-02-01 13:38:57 +00:00
my $ hash = $ param - > { hash } ;
my $ name = $ hash - > { NAME } ;
my $ addr = $ hash - > { ADDR } ;
my $ port = $ hash - > { PORT } ;
my $ opmode = $ hash - > { OPMODE } ;
2020-01-19 14:21:41 +00:00
my ( $ error , $ errorcode , $ success ) ;
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 ) ;
SSCal_checkretry ( $ name , 1 ) ;
return ;
} elsif ( $ myjson ne "" ) {
# Evaluiere ob Daten im JSON-Format empfangen wurden
( $ hash , $ success , $ myjson ) = SSCal_evaljson ( $ hash , $ myjson ) ;
unless ( $ success ) {
Log3 ( $ name , 4 , "$name - Data returned: " . $ myjson ) ;
SSCal_checkretry ( $ name , 1 ) ;
return ;
}
my $ data = decode_json ( $ myjson ) ;
# Logausgabe decodierte JSON Daten
Log3 ( $ name , 5 , "$name - JSON returned: " . Dumper $ data ) ;
$ success = $ data - > { 'success' } ;
if ( $ success ) {
my $ logstr ;
# Pfad und Maxversion von "SYNO.API.Auth" ermitteln
my $ apiauthpath = $ data - > { data } - > { $ SSCal_api { APIAUTH } { NAME } } - > { path } ;
$ apiauthpath =~ tr /_/ / d if ( defined ( $ apiauthpath ) ) ;
my $ apiauthmaxver = $ data - > { data } - > { $ SSCal_api { APIAUTH } { NAME } } - > { maxVersion } ;
$ logstr = defined ( $ apiauthpath ) ? "Path of $SSCal_api{APIAUTH}{NAME} selected: $apiauthpath" : "Path of $SSCal_api{APIAUTH}{NAME} undefined - Synology cal Server may be stopped" ;
Log3 ( $ name , 4 , "$name - $logstr" ) ;
$ logstr = defined ( $ apiauthmaxver ) ? "MaxVersion of $SSCal_api{APIAUTH}{NAME} selected: $apiauthmaxver" : "MaxVersion of $SSCal_api{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 } - > { $ SSCal_api { CALCAL } { NAME } } - > { path } ;
$ apicalpath =~ tr /_/ / d if ( defined ( $ apicalpath ) ) ;
my $ apicalmaxver = $ data - > { data } - > { $ SSCal_api { CALCAL } { NAME } } - > { maxVersion } ;
$ logstr = defined ( $ apicalpath ) ? "Path of $SSCal_api{CALCAL}{NAME} selected: $apicalpath" : "Path of $SSCal_api{CALCAL}{NAME} undefined - Synology cal Server may be stopped" ;
Log3 ( $ name , 4 , "$name - $logstr" ) ;
$ logstr = defined ( $ apicalmaxver ) ? "MaxVersion of $SSCal_api{CALCAL}{NAME} selected: $apicalmaxver" : "MaxVersion of $SSCal_api{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 } - > { $ SSCal_api { CALEVENT } { NAME } } - > { path } ;
$ apievtpath =~ tr /_/ / d if ( defined ( $ apievtpath ) ) ;
my $ apievtmaxver = $ data - > { data } - > { $ SSCal_api { CALEVENT } { NAME } } - > { maxVersion } ;
$ logstr = defined ( $ apievtpath ) ? "Path of $SSCal_api{CALEVENT}{NAME} selected: $apievtpath" : "Path of $SSCal_api{CALEVENT}{NAME} undefined - Synology cal Server may be stopped" ;
Log3 ( $ name , 4 , "$name - $logstr" ) ;
$ logstr = defined ( $ apievtmaxver ) ? "MaxVersion of $SSCal_api{CALEVENT}{NAME} selected: $apievtmaxver" : "MaxVersion of $SSCal_api{CALEVENT}{NAME} undefined - Synology cal Server may be stopped" ;
Log3 ( $ name , 4 , "$name - $logstr" ) ;
# Pfad und Maxversion von "SYNO.Cal.Sharing" ermitteln
my $ apisharepath = $ data - > { data } - > { $ SSCal_api { CALSHARE } { NAME } } - > { path } ;
$ apisharepath =~ tr /_/ / d if ( defined ( $ apisharepath ) ) ;
my $ apisharemaxver = $ data - > { data } - > { $ SSCal_api { CALSHARE } { NAME } } - > { maxVersion } ;
$ logstr = defined ( $ apisharepath ) ? "Path of $SSCal_api{CALSHARE}{NAME} selected: $apisharepath" : "Path of $SSCal_api{CALSHARE}{NAME} undefined - Synology cal Server may be stopped" ;
Log3 ( $ name , 4 , "$name - $logstr" ) ;
$ logstr = defined ( $ apisharemaxver ) ? "MaxVersion of $SSCal_api{CALSHARE}{NAME} selected: $apisharemaxver" : "MaxVersion of $SSCal_api{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 } - > { $ SSCal_api { CALTODO } { NAME } } - > { path } ;
$ apitodopath =~ tr /_/ / d if ( defined ( $ apitodopath ) ) ;
my $ apitodomaxver = $ data - > { data } - > { $ SSCal_api { CALTODO } { NAME } } - > { maxVersion } ;
$ logstr = defined ( $ apitodopath ) ? "Path of $SSCal_api{CALTODO}{NAME} selected: $apitodopath" : "Path of $SSCal_api{CALTODO}{NAME} undefined - Synology cal Server may be stopped" ;
Log3 ( $ name , 4 , "$name - $logstr" ) ;
$ logstr = defined ( $ apitodomaxver ) ? "MaxVersion of $SSCal_api{CALTODO}{NAME} selected: $apitodomaxver" : "MaxVersion of $SSCal_api{CALTODO}{NAME} undefined - Synology cal Server may be stopped" ;
2020-02-01 13:38:57 +00:00
Log3 ( $ name , 4 , "$name - $logstr" ) ;
# Pfad und Maxversion von "SYNO.API.Info" ermitteln
my $ apiinfopath = $ data - > { data } - > { $ SSCal_api { APIINFO } { NAME } } - > { path } ;
$ apiinfopath =~ tr /_/ / d if ( defined ( $ apiinfopath ) ) ;
my $ apiinfomaxver = $ data - > { data } - > { $ SSCal_api { APIINFO } { NAME } } - > { maxVersion } ;
$ logstr = defined ( $ apiinfopath ) ? "Path of $SSCal_api{APIINFO}{NAME} selected: $apiinfopath" : "Path of $SSCal_api{APIINFO}{NAME} undefined - Synology cal Server may be stopped" ;
Log3 ( $ name , 4 , "$name - $logstr" ) ;
$ logstr = defined ( $ apiinfomaxver ) ? "MaxVersion of $SSCal_api{APIINFO}{NAME} selected: $apiinfomaxver" : "MaxVersion of $SSCal_api{APIINFO}{NAME} undefined - Synology cal Server may be stopped" ;
Log3 ( $ name , 4 , "$name - $logstr" ) ;
2020-01-19 14:21:41 +00:00
# ermittelte Werte in $hash einfügen
2020-02-01 13:38:57 +00:00
$ SSCal_api { APIINFO } { PATH } = $ apiinfopath ;
$ SSCal_api { APIINFO } { MAX } = $ apiinfomaxver ;
2020-01-19 14:21:41 +00:00
$ SSCal_api { APIAUTH } { PATH } = $ apiauthpath ;
$ SSCal_api { APIAUTH } { MAX } = $ apiauthmaxver ;
$ SSCal_api { CALCAL } { PATH } = $ apicalpath ;
$ SSCal_api { CALCAL } { MAX } = $ apicalmaxver ;
$ SSCal_api { CALEVENT } { PATH } = $ apievtpath ;
$ SSCal_api { CALEVENT } { MAX } = $ apievtmaxver ;
$ SSCal_api { CALSHARE } { PATH } = $ apisharepath ;
$ SSCal_api { CALSHARE } { MAX } = $ apisharemaxver ;
$ SSCal_api { CALTODO } { PATH } = $ apitodopath ;
$ SSCal_api { CALTODO } { MAX } = $ apitodomaxver ;
# API values sind gesetzt in Hash
$ hash - > { HELPER } { APIPARSET } = 1 ;
2020-02-01 13:38:57 +00:00
if ( $ opmode eq "apiInfo" ) { # API Infos in Popup anzeigen
my $ out = "<html>" ;
$ out . = "<b>Synology Calendar API Info</b> <br><br>" ;
$ out . = "<table class=\"roomoverview\" style=\"text-align:left; border:1px solid; padding:5px; border-spacing:5px; margin-left:auto; margin-right:auto;\">" ;
$ out . = "<tr><td> <b>API</b> </td><td> <b>Path</b> </td><td> <b>Version</b> </td></tr>" ;
$ out . = "<tr><td> </td><td> </td><td> </td><td> </td><td> </td></tr>" ;
foreach my $ key ( keys % SSCal_api ) {
my $ apiname = $ SSCal_api { $ key } { NAME } ;
my $ apipath = $ SSCal_api { $ key } { PATH } ;
my $ apiver = $ SSCal_api { $ key } { MAX } ;
$ out . = "<tr><td> $apiname </td><td> $apipath </td><td> $apiver</td></tr>" ;
}
$ out . = "</table>" ;
$ out . = "</html>" ;
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 } ) ;
SSCal_checkretry ( $ name , 0 ) ;
return ;
}
2020-01-19 14:21:41 +00:00
} else {
$ errorcode = "806" ;
$ error = SSCal_experror ( $ hash , $ errorcode ) ; # Fehlertext zum Errorcode ermitteln
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdateIfChanged ( $ hash , "Errorcode" , $ errorcode ) ;
readingsBulkUpdateIfChanged ( $ hash , "Error" , $ error ) ;
readingsBulkUpdate ( $ hash , "state" , "Error" ) ;
readingsEndUpdate ( $ hash , 1 ) ;
Log3 ( $ name , 2 , "$name - ERROR - the API-Query couldn't be executed successfully" ) ;
SSCal_checkretry ( $ name , 1 ) ;
return ;
}
}
return SSCal_checkSID ( $ name ) ;
}
#############################################################################################
# Ausführung Operation
#############################################################################################
sub SSCal_calop ($) {
my ( $ name ) = @ _ ;
my $ hash = $ defs { $ name } ;
my $ prot = $ hash - > { PROT } ;
my $ addr = $ hash - > { ADDR } ;
my $ port = $ hash - > { PORT } ;
my $ sid = $ hash - > { HELPER } { SID } ;
my ( $ url , $ timeout , $ param , $ error , $ errorcode ) ;
my $ idx = $ hash - > { OPIDX } ;
my $ opmode = $ hash - > { OPMODE } ;
my $ method = $ data { SSCal } { $ name } { sendqueue } { entries } { $ idx } { method } ;
my $ api = $ data { SSCal } { $ name } { sendqueue } { entries } { $ idx } { api } ;
my $ params = $ data { SSCal } { $ name } { sendqueue } { entries } { $ idx } { params } ;
Log3 ( $ name , 4 , "$name - start SendQueue entry index \"$idx\" ($hash->{OPMODE}) for operation." ) ;
$ timeout = AttrVal ( $ name , "timeout" , 20 ) ;
Log3 ( $ name , 5 , "$name - HTTP-Call will be done with timeout: $timeout s" ) ;
$ url = "$prot://$addr:$port/webapi/" . $ SSCal_api { $ api } { PATH } . "?api=" . $ SSCal_api { $ api } { NAME } . "&version=" . $ SSCal_api { $ api } { MAX } . "&method=$method" . $ params . "&_sid=$sid" ;
2020-02-01 13:38:57 +00:00
if ( $ opmode eq "deleteEventId" && $ api eq "CALEVENT" ) { # Workaround !!! Methode delete funktioniert nicht mit SYNO.Cal.Event version > 1
$ url = "$prot://$addr:$port/webapi/" . $ SSCal_api { $ api } { PATH } . "?api=" . $ SSCal_api { $ api } { NAME } . "&version=1&method=$method" . $ params . "&_sid=$sid" ;
}
2020-01-19 14:21:41 +00:00
my $ part = $ url ;
if ( AttrVal ( $ name , "showPassInLog" , "0" ) == 1 ) {
Log3 ( $ name , 4 , "$name - Call-Out: $url" ) ;
} else {
$ part =~ s/$sid/<secret>/ ;
Log3 ( $ name , 4 , "$name - Call-Out: $part" ) ;
}
$ param = {
url = > $ url ,
timeout = > $ timeout ,
hash = > $ hash ,
method = > "GET" ,
header = > "Accept: application/json" ,
callback = > \ & SSCal_calop_parse
} ;
HttpUtils_NonblockingGet ( $ param ) ;
}
#############################################################################################
# Callback from SSCal_calop
#############################################################################################
sub SSCal_calop_parse ($) {
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 $ 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
Log3 ( $ name , 2 , "$name - ERROR message: $err" ) ;
$ errorcode = "none" ;
$ errorcode = "800" if ( $ err =~ /: malformed or unsupported URL$/s ) ;
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdateIfChanged ( $ hash , "Error" , $ err ) ;
readingsBulkUpdateIfChanged ( $ hash , "Errorcode" , $ errorcode ) ;
readingsBulkUpdate ( $ hash , "state" , "Error" ) ;
readingsEndUpdate ( $ hash , 1 ) ;
SSCal_checkretry ( $ name , 1 ) ;
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 ) = SSCal_evaljson ( $ hash , $ myjson ) ;
unless ( $ success ) {
Log3 ( $ name , 4 , "$name - Data returned: " . $ myjson ) ;
SSCal_checkretry ( $ name , 1 ) ;
return ;
}
$ data = decode_json ( $ myjson ) ;
# Logausgabe decodierte JSON Daten
Log3 ( $ name , 5 , "$name - JSON returned: " . Dumper $ data ) ;
$ success = $ data - > { 'success' } ;
if ( $ success ) {
if ( $ opmode eq "listcal" ) { # alle Kalender abrufen
my % calendars = ( ) ;
my ( $ cals , $ dnm , $ typ , $ oid , $ des , $ prv , $ psi ) ;
my $ i = 0 ;
my $ out = "<html>" ;
$ out . = "<b>Synology Calendar List</b> <br><br>" ;
$ out . = "<table class=\"roomoverview\" style=\"text-align:left; border:1px solid; padding:5px; border-spacing:5px; margin-left:auto; margin-right:auto;\">" ;
$ out . = "<tr><td> <b>Calendar</b> </td><td> <b>ID</b> </td><td> <b>Type</b> </td><td> <b>Description</b> </td><td> <b>Privilege</b> </td><td> <b>Public share ID</b> </td><td></tr>" ;
$ out . = "<tr><td> </td><td> </td><td> </td><td> </td><td> </td><td></tr>" ;
while ( $ data - > { data } [ $ i ] ) {
$ dnm = $ data - > { data } [ $ i ] { cal_displayname } ;
next if ( ! $ dnm ) ;
$ typ = "Event" if ( $ data - > { data } [ $ i ] { is_evt } ) ;
$ typ = "ToDo" if ( $ data - > { data } [ $ i ] { is_todo } ) ;
$ oid = $ data - > { data } [ $ i ] { original_cal_id } ;
$ des = encode ( "UTF-8" , $ data - > { data } [ $ i ] { cal_description } ) ;
$ prv = $ data - > { data } [ $ i ] { cal_privilege } ;
$ psi = $ data - > { data } [ $ i ] { cal_public_sharing_id } ;
$ psi = $ psi ? $ psi: "" ;
$ calendars { $ dnm } { id } = $ oid ;
$ calendars { $ dnm } { description } = $ des ;
$ calendars { $ dnm } { privilege } = $ prv ;
$ calendars { $ dnm } { publicshareid } = $ psi ;
$ calendars { $ dnm } { type } = $ typ ;
$ cals . = "," if ( $ cals ) ;
$ cals . = $ dnm ;
$ out . = "<tr><td> $dnm </td><td> $oid </td><td> $typ</td><td> $des </td><td> $prv </td><td> $psi </td><td></tr>" ;
$ i + + ;
}
$ out . = "</table>" ;
$ out . = "</html>" ;
$ hash - > { HELPER } { CALENDARS } = \ % calendars if ( % calendars ) ;
$ hash - > { HELPER } { CALFETCHED } = 1 ;
my @ newa ;
my $ list = $ modules { $ hash - > { TYPE } } { AttrList } ;
my @ deva = split ( " " , $ list ) ;
foreach ( @ deva ) {
push @ newa , $ _ if ( $ _ !~ /usedCalendars:/ ) ;
}
$ cals =~ s/ /#/g if ( $ cals ) ;
push @ newa , ( $ cals ? "usedCalendars:multiple-strict,$cals " : "usedCalendars:--no#Calendar#selectable--" ) ;
$ hash - > { ".AttrList" } = join ( " " , @ newa ) ; # Device spezifische AttrList, überschreibt Modul AttrList !
# 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 } ) ;
SSCal_checkretry ( $ name , 0 ) ;
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
2020-02-01 13:38:57 +00:00
delete $ data { SSCal } { $ name } { eventlist } ; # zentrales Event/ToDo Hash löschen
2020-01-19 14:21:41 +00:00
$ hash - > { eventlist } = $ data ; # Data-Hashreferenz im Hash speichern
if ( $ am ) { # Extrahieren der Events asynchron (nicht-blockierend)
Log3 ( $ name , 4 , "$name - Event parse mode: asynchronous" ) ;
my $ timeout = AttrVal ( $ name , "timeout" , 20 ) + 180 ;
$ hash - > { HELPER } { RUNNING_PID } = BlockingCall ( "SSCal_extractEventlist" , $ name , "SSCal_createReadings" , $ timeout , "SSCal_blockingTimeout" , $ hash ) ;
$ 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" ) ;
SSCal_extractEventlist ( $ name ) ;
}
2020-01-31 15:27:30 +00:00
} elsif ( $ opmode eq "todolist" ) { # ToDo's der ausgewählten Tasks-Kalender aufbereiten
2020-02-01 13:38:57 +00:00
delete $ data { SSCal } { $ name } { eventlist } ; # zentrales Event/ToDo Hash löschen
2020-01-31 15:27:30 +00:00
$ hash - > { eventlist } = $ data ; # Data-Hashreferenz im Hash speichern
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 ( "SSCal_extractToDolist" , $ name , "SSCal_createReadings" , $ timeout , "SSCal_blockingTimeout" , $ hash ) ;
$ hash - > { HELPER } { RUNNING_PID } { loglevel } = 5 if ( $ hash - > { HELPER } { RUNNING_PID } ) ; # Forum #77057
} else { # Extrahieren der ToDos synchron (blockierend)
Log3 ( $ name , 4 , "$name - Task parse mode: synchronous" ) ;
2020-02-02 18:47:04 +00:00
SSCal_extractToDolist ( $ name ) ;
2020-01-31 15:27:30 +00:00
}
2020-02-01 13:38:57 +00:00
} elsif ( $ opmode eq "cleanCompleteTasks" ) { # abgeschlossene ToDos wurden gelöscht
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdateIfChanged ( $ hash , "Errorcode" , "none" ) ;
readingsBulkUpdateIfChanged ( $ hash , "Error" , "none" ) ;
readingsBulkUpdate ( $ hash , "state" , "done" ) ;
readingsEndUpdate ( $ hash , 1 ) ;
Log3 ( $ name , 3 , "$name - All completed tasks were deleted from selected ToDo lists" ) ;
SSCal_checkretry ( $ name , 0 ) ;
} elsif ( $ opmode eq "deleteEventId" ) { # ein Kalendereintrag mit Event Id wurde gelöscht
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdateIfChanged ( $ hash , "Errorcode" , "none" ) ;
readingsBulkUpdateIfChanged ( $ hash , "Error" , "none" ) ;
readingsBulkUpdate ( $ hash , "state" , "done" ) ;
readingsEndUpdate ( $ hash , 1 ) ;
Log3 ( $ name , 3 , "$name - The specified event id was deleted" ) ;
# Queuedefinition sichern vor checkretry
my $ idx = $ hash - > { OPIDX } ;
my $ api = $ data { SSCal } { $ name } { sendqueue } { entries } { $ idx } { api } ;
my $ set = ( $ api eq "CALEVENT" ) ? "calEventList" : "calToDoList" ;
SSCal_checkretry ( $ name , 0 ) ;
# Kalendereinträge neu einlesen nach dem löschen Event Id
CommandSet ( undef , "$name $set" ) ;
}
2020-01-19 14:21:41 +00:00
} else {
# die API-Operation war fehlerhaft
# Errorcode aus JSON ermitteln
$ errorcode = $ data - > { error } - > { code } ;
$ cherror = $ data - > { error } - > { errors } ; # vom cal gelieferter Fehler
$ error = SSCal_experror ( $ hash , $ errorcode ) ; # Fehlertext zum Errorcode ermitteln
if ( $ error =~ /not found/ ) {
$ error . = " New error: " . ( $ cherror ? $ cherror: "" ) ;
}
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdateIfChanged ( $ hash , "Errorcode" , $ errorcode ) ;
readingsBulkUpdateIfChanged ( $ hash , "Error" , $ error ) ;
readingsBulkUpdate ( $ hash , "state" , "Error" ) ;
readingsEndUpdate ( $ hash , 1 ) ;
Log3 ( $ name , 2 , "$name - ERROR - Operation $opmode was not successful. Errorcode: $errorcode - $error" ) ;
SSCal_checkretry ( $ name , 1 ) ;
}
undef $ data ;
undef $ myjson ;
}
return ;
}
#############################################################################################
2020-01-31 15:27:30 +00:00
# Extrahiert empfangene Kalendertermine (Events)
2020-01-19 14:21:41 +00:00
#############################################################################################
sub SSCal_extractEventlist ($) {
my ( $ name ) = @ _ ;
my $ hash = $ defs { $ name } ;
my $ data = delete $ hash - > { eventlist } ;
my $ am = AttrVal ( $ name , "asyncMode" , 0 ) ;
2020-01-31 15:27:30 +00:00
my ( $ tz , $ bdate , $ btime , $ bts , $ edate , $ etime , $ ets , $ ci , $ bi , $ ei , $ startEndDiff ) ;
my ( $ bmday , $ bmonth , $ emday , $ emonth , $ byear , $ eyear , $ nbdate , $ nbtime , $ nbts , $ nedate , $ netime , $ nets ) ;
2020-01-19 14:21:41 +00:00
my @ row_array ;
my ( undef , $ tstart , $ tend ) = SSCal_timeEdge ( $ name ) ; # Sollstart- und Sollendezeit der Kalenderereignisse ermitteln
my $ datetimestart = FmtDateTime ( $ tstart ) ;
my $ datetimeend = FmtDateTime ( $ tend ) ;
my $ n = 0 ;
foreach my $ key ( keys % { $ data - > { data } } ) {
my $ i = 0 ;
while ( $ data - > { data } { $ key } [ $ i ] ) {
my $ ignore = 0 ;
my $ done = 0 ;
( $ nbdate , $ nedate ) = ( "" , "" ) ;
2020-02-09 10:21:37 +00:00
my $ isallday = $ data - > { data } { $ key } [ $ i ] { is_all_day } ;
( $ bi , $ tz , $ bdate , $ btime , $ bts ) = SSCal_explodeDateTime ( $ hash , $ data - > { data } { $ key } [ $ i ] { dtstart } ) ; # Beginn des Events
( $ ei , undef , $ edate , $ etime , $ ets ) = SSCal_explodeDateTime ( $ hash , $ data - > { data } { $ key } [ $ i ] { dtend } , $ isallday ) ; # Ende des Events
2020-01-19 14:21:41 +00:00
$ bdate =~ /(\d{4})-(\d{2})-(\d{2})/ ;
$ bmday = $ 3 ;
$ bmonth = $ 2 ;
$ byear = $ 1 ;
$ nbtime = $ btime ;
$ edate =~ /(\d{4})-(\d{2})-(\d{2})/ ;
$ emday = $ 3 ;
$ emonth = $ 2 ;
$ eyear = $ 1 ;
$ netime = $ etime ;
2020-02-09 10:21:37 +00:00
# Bugfix API - wenn is_all_day und an erster Stelle im 'data' Ergebnis des API-Calls ist Endedate/time nicht korrekt !
if ( $ isallday && ( $ bdate ne $ edate ) && $ netime =~ /^00:59:59$/ ) {
$ eyear = $ byear ;
$ emonth = $ bmonth ;
$ emday = $ bmday ;
$ nbtime =~ s/://g ;
$ netime = "235959" ;
( $ bi , undef , $ bdate , $ btime , $ bts ) = SSCal_explodeDateTime ( $ hash , $ byear . $ bmonth . $ bmday . "T" . $ nbtime ) ;
( $ ei , undef , $ edate , $ etime , $ ets ) = SSCal_explodeDateTime ( $ hash , $ eyear . $ emonth . $ emday . "T" . $ netime ) ;
}
$ startEndDiff = $ ets - $ bts ; # Differenz Event Ende / Start in Sekunden
2020-01-19 14:21:41 +00:00
if ( ! $ data - > { data } { $ key } [ $ i ] { is_repeat_evt } ) { # einmaliger Event
Log3 ( $ name , 5 , "$name - Single event Begin: $bdate, End: $edate" ) ;
if ( $ ets < $ tstart || $ bts > $ tend ) {
Log3 ( $ name , 4 , "$name - Ignore single event -> $data->{data}{$key}[$i]{summary} start: $bdate $btime, end: $edate $etime" ) ;
$ ignore = 1 ;
$ done = 0 ;
} else {
@ row_array = SSCal_writeValuesToArray ( $ name , $ n , $ data - > { data } { $ key } [ $ i ] , $ tz , $ bdate , $ btime , $ bts , $ edate , $ etime , $ ets , \ @ row_array ) ;
$ ignore = 0 ;
$ done = 1 ;
}
} 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 ) ;
my $ rr = $ data - > { data } { $ key } [ $ i ] { evt_repeat_setting } { repeat_rule } ;
# Format: FREQ=YEARLY;COUNT=1;INTERVAL=2;BYMONTHDAY=15;BYMONTH=10;UNTIL=2020-12-31T00:00:00
my @ para = split ( ";" , $ rr ) ;
foreach my $ par ( @ para ) {
my ( $ p1 , $ p2 ) = split ( "=" , $ par ) ;
if ( $ p1 eq "FREQ" ) {
$ freq = $ p2 ;
} elsif ( $ p1 eq "COUNT" ) { # Event endet automatisch nach x Wiederholungen
2020-02-16 22:56:53 +00:00
$ count = $ p2 - 1 ;
2020-01-19 14:21:41 +00:00
} elsif ( $ p1 eq "INTERVAL" ) { # Wiederholungsintervall
$ interval = $ p2 ;
} elsif ( $ p1 eq "UNTIL" ) { # festes Intervallende angegeben
$ until = $ p2 ;
$ until =~ s/[-:]//g ;
( undef , undef , undef , undef , $ uets ) = SSCal_explodeDateTime ( $ hash , $ until ) ;
if ( $ uets < $ tstart ) {
Log3 ( $ name , 4 , "$name - Ignore recurring event -> $data->{data}{$key}[$i]{summary} , interval end \"$nedate $netime\" is less than selection start \"$datetimestart\"" ) ;
$ ignore = 1 ;
}
} 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)
$ byday = $ p2 ;
}
}
$ count = $ count ? $ count:9999999 ; # $count "unendlich" wenn kein COUNT angegeben
$ interval = $ interval ? $ interval:1 ;
$ bymonthday = $ bymonthday ? $ bymonthday: "" ;
$ byday = $ byday ? $ byday: "" ;
$ until = $ until ? $ until: "" ;
Log3 ( $ name , 4 , "$name - Recurring params - FREQ: $freq, COUNT: $count, INTERVAL: $interval, BYMONTHDAY: $bymonthday, BYDAY: $byday, UNTIL: $until" ) ;
if ( $ freq eq "YEARLY" ) { # jährliche Wiederholung
for ( $ ci = - 1 ; $ ci < ( $ count * $ interval ) ; $ ci += $ interval ) {
$ byear += ( $ ci >= 0 ? 1 : 0 ) ;
$ eyear += ( $ ci >= 0 ? 1 : 0 ) ;
$ nbtime =~ s/://g ;
$ netime =~ s/://g ;
( $ bi , undef , $ nbdate , $ nbtime , $ nbts ) = SSCal_explodeDateTime ( $ hash , $ byear . $ bmonth . $ bmday . "T" . $ nbtime ) ; # Beginn des Wiederholungsevents
( $ ei , undef , $ nedate , $ netime , $ nets ) = SSCal_explodeDateTime ( $ hash , $ eyear . $ emonth . $ emday . "T" . $ netime ) ; # Ende des Wiederholungsevents
Log3 ( $ name , 5 , "$name - YEARLY event - Begin: $nbdate $nbtime, End: $nedate $netime" ) ;
if ( defined $ uets && ( $ uets < $ nbts ) ) { # Event Ende (UNTIL) kleiner aktueller Select Start
Log3 ( $ name , 4 , "$name - Ignore YEARLY event due to UNTIL -> $data->{data}{$key}[$i]{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
Log3 ( $ name , 4 , "$name - Ignore YEARLY event -> $data->{data}{$key}[$i]{summary} , start: $nbdate $nbtime, end: $nedate $netime" ) ;
$ ignore = 1 ;
$ done = 0 ;
} else {
$ bdate = $ nbdate ? $ nbdate: $ bdate ;
$ btime = $ nbtime ? $ nbtime: $ btime ;
$ bts = $ nbts ? $ nbts: $ bts ;
$ edate = $ nedate ? $ nedate: $ edate ;
$ etime = $ netime ? $ netime: $ etime ;
$ ets = $ nets ? $ nets: $ ets ;
@ row_array = SSCal_writeValuesToArray ( $ name , $ n , $ data - > { data } { $ key } [ $ i ] , $ tz , $ bdate , $ btime , $ bts , $ edate , $ etime , $ ets , \ @ row_array ) ;
$ ignore = 0 ;
$ done = 1 ;
$ n + + ;
next ;
}
last if ( ( defined $ uets && ( $ uets < $ nbts ) ) || $ nbts > $ tend ) ;
}
}
if ( $ freq eq "MONTHLY" ) { # monatliche Wiederholung
if ( $ bymonthday ) { # Wiederholungseigenschaft am Tag X des Monats
for ( $ ci = - 1 ; $ ci < ( $ count * $ interval ) ; $ ci += $ interval ) {
$ bmonth += $ interval ;
$ byear += int ( $ bmonth / 13 ) ;
$ bmonth % = 12 if ( $ bmonth > 12 ) ;
$ bmonth = sprintf ( "%02d" , $ bmonth ) ;
$ emonth += $ interval ;
$ eyear += int ( $ emonth / 13 ) ;
$ emonth % = 12 if ( $ emonth > 12 ) ;
$ emonth = sprintf ( "%02d" , $ emonth ) ;
$ nbtime =~ s/://g ;
$ netime =~ s/://g ;
( $ bi , undef , $ nbdate , $ nbtime , $ nbts ) = SSCal_explodeDateTime ( $ hash , $ byear . $ bmonth . $ bmday . "T" . $ nbtime ) ; # Beginn des Wiederholungsevents
( $ ei , undef , $ nedate , $ netime , $ nets ) = SSCal_explodeDateTime ( $ hash , $ eyear . $ emonth . $ emday . "T" . $ netime ) ; # Ende des Wiederholungsevents
Log3 ( $ name , 5 , "$name - MONTHLY event - Begin: $nbdate $nbtime, End: $nedate $netime" ) ;
if ( defined $ uets && ( $ uets < $ nbts ) ) { # Event Ende (UNTIL) kleiner aktueller Select Start
Log3 ( $ name , 4 , "$name - Ignore MONTHLY event due to UNTIL -> $data->{data}{$key}[$i]{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
Log3 ( $ name , 4 , "$name - Ignore MONTHLY event -> $data->{data}{$key}[$i]{summary} , start: $nbdate $nbtime, end: $nedate $netime" ) ;
$ ignore = 1 ;
$ done = 0 ;
} else {
$ bdate = $ nbdate ? $ nbdate: $ bdate ;
$ btime = $ nbtime ? $ nbtime: $ btime ;
$ bts = $ nbts ? $ nbts: $ bts ;
$ edate = $ nedate ? $ nedate: $ edate ;
$ etime = $ netime ? $ netime: $ etime ;
$ ets = $ nets ? $ nets: $ ets ;
@ row_array = SSCal_writeValuesToArray ( $ name , $ n , $ data - > { data } { $ key } [ $ i ] , $ tz , $ bdate , $ btime , $ bts , $ edate , $ etime , $ ets , \ @ row_array ) ;
$ ignore = 0 ;
$ done = 1 ;
$ n + + ;
next ;
}
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 , $ rbYday ) ;
my @ ByDays = split ( "," , $ byday ) ; # Array der Wiederholungstage
foreach ( @ ByDays ) {
2020-02-09 10:21:37 +00:00
my $ rByDay = $ _ ; # das erste Wiederholungselement
2020-01-19 14:21:41 +00:00
my $ rByDayLength = length ( $ rByDay ) ; # die Länge des Strings
2020-02-09 10:21:37 +00:00
my $ rDayStr ; # Tag auf den das Datum gesetzt werden soll
my $ rDayInterval ; # z.B. 2 = 2nd Tag des Monats oder -1 = letzter Tag des Monats
2020-01-19 14:21:41 +00:00
if ( $ rByDayLength > 2 ) {
$ rDayStr = substr ( $ rByDay , - 2 ) ;
$ rDayInterval = int ( substr ( $ rByDay , 0 , $ rByDayLength - 2 ) ) ;
} else {
$ rDayStr = $ rByDay ;
$ rDayInterval = 1 ;
}
my @ weekdays = qw( SU MO TU WE TH FR SA ) ;
my ( $ rDayOfWeek ) = grep { $ weekdays [ $ _ ] eq $ rDayStr } 0 .. $# weekdays ; # liefert Nr des Wochentages: SU = 0 ... SA = 6
for ( $ ci = - 1 ; $ ci < ( $ count ) ; $ ci + + ) {
if ( $ rDayInterval > 0 ) { # Angabe "jeder x Wochentag" ist positiv (-2 wäre z.B. vom Ende des Monats zu zähelen)
$ bmonth += $ interval ;
$ byear += int ( $ bmonth / 13 ) ;
$ bmonth % = 12 if ( $ bmonth > 12 ) ;
$ bmonth = sprintf ( "%02d" , $ bmonth ) ;
( $ nbhh , $ nbmm , $ nbss ) = split ( ":" , $ nbtime ) ;
my $ firstOfNextMonth = fhemTimeLocal ( $ nbss , $ nbmm , $ nbhh , 1 , $ bmonth - 1 , $ byear - 1900 ) ;
( $ nbss , $ nbmm , $ nbhh , $ bmday , $ bmonth , $ byear , $ rDayOfWeekNew , undef , undef ) = localtime ( $ firstOfNextMonth ) ; # den 1. des Monats sowie die dazu gehörige Nr. des Wochentages
if ( $ rDayOfWeekNew <= $ rDayOfWeek ) { # Nr Wochentag des 1. des Monats <= als Wiederholungstag
$ rDaysToAddOrSub = $ rDayOfWeek - $ rDayOfWeekNew ;
} else {
$ rDaysToAddOrSub = 7 - $ rDayOfWeekNew + $ rDayOfWeek ;
}
$ rDaysToAddOrSub += ( 7 * ( $ rDayInterval - 1 ) ) ; # addiere Tagesintervall, z.B. 4th Freitag ...
$ rNewTime = SSCal_plusNSeconds ( $ firstOfNextMonth , 86400 * $ rDaysToAddOrSub , 1 ) ;
( $ nbss , $ nbmm , $ nbhh , $ bmday , $ bmonth , $ byear , $ ness , $ nemm , $ nehh , $ emday , $ emonth , $ eyear ) = SSCal_DTfromStartandDiff ( $ rNewTime , $ startEndDiff ) ;
} else {
Log3 ( $ name , 2 , "$name - WARNING - negative values for BYDAY are currently not implemented and will be ignored" ) ;
$ ignore = 1 ;
$ done = 0 ;
$ n + + ;
next ;
}
$ nbtime = $ nbhh . $ nbmm . $ nbss ;
$ netime = $ nehh . $ nemm . $ ness ;
( $ bi , undef , $ nbdate , $ nbtime , $ nbts ) = SSCal_explodeDateTime ( $ hash , $ byear . $ bmonth . $ bmday . "T" . $ nbtime ) ; # Beginn des Wiederholungsevents
( $ ei , undef , $ nedate , $ netime , $ nets ) = SSCal_explodeDateTime ( $ hash , $ eyear . $ emonth . $ emday . "T" . $ netime ) ; # Ende des Wiederholungsevents
Log3 ( $ name , 5 , "$name - MONTHLY event - Begin: $nbdate $nbtime, End: $nedate $netime" ) ;
if ( defined $ uets && ( $ uets < $ nbts ) ) { # Event Ende (UNTIL) kleiner aktueller Select Start
Log3 ( $ name , 4 , "$name - Ignore MONTHLY event due to UNTIL -> $data->{data}{$key}[$i]{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
Log3 ( $ name , 4 , "$name - Ignore MONTHLY event -> $data->{data}{$key}[$i]{summary} , start: $nbdate $nbtime, end: $nedate $netime" ) ;
$ ignore = 1 ;
$ done = 0 ;
} else {
$ bdate = $ nbdate ? $ nbdate: $ bdate ;
$ btime = $ nbtime ? $ nbtime: $ btime ;
$ bts = $ nbts ? $ nbts: $ bts ;
$ edate = $ nedate ? $ nedate: $ edate ;
$ etime = $ netime ? $ netime: $ etime ;
$ ets = $ nets ? $ nets: $ ets ;
@ row_array = SSCal_writeValuesToArray ( $ name , $ n , $ data - > { data } { $ key } [ $ i ] , $ tz , $ bdate , $ btime , $ bts , $ edate , $ etime , $ ets , \ @ row_array ) ;
$ ignore = 0 ;
$ done = 1 ;
$ n + + ;
next ;
}
last if ( ( defined $ uets && ( $ uets < $ nbts ) ) || $ nbts > $ tend ) ;
}
}
}
}
if ( $ freq eq "WEEKLY" ) { # wöchentliche Wiederholung
if ( $ byday ) { # Wiederholungseigenschaft -> Wochentag z.B. 2WE,-1SU,4FR (kann auch Liste bei WEEKLY sein)
my ( $ nbhh , $ nbmm , $ nbss , $ nehh , $ nemm , $ ness , $ rDayOfWeekNew , $ rDaysToAddOrSub ) ;
my @ ByDays = split ( "," , $ byday ) ; # Array der Wiederholungstage
my $ btsstart = $ bts ;
foreach ( @ ByDays ) {
my $ rNewTime = $ btsstart ;
2020-01-31 15:27:30 +00:00
my $ rByDay = $ _ ; # das erste Wiederholungselement
2020-01-19 14:21:41 +00:00
my $ rByDayLength = length ( $ rByDay ) ; # die Länge des Strings
2020-01-31 15:27:30 +00:00
my $ rDayStr ; # Tag auf den das Datum gesetzt werden soll
my $ rDayInterval ; # z.B. 2 = 2nd Tag des Monats oder -1 = letzter Tag des Monats
2020-01-19 14:21:41 +00:00
if ( $ rByDayLength > 2 ) {
$ rDayStr = substr ( $ rByDay , - 2 ) ;
$ rDayInterval = int ( substr ( $ rByDay , 0 , $ rByDayLength - 2 ) ) ;
} else {
$ rDayStr = $ rByDay ;
$ rDayInterval = 1 ;
}
my @ weekdays = qw( SU MO TU WE TH FR SA ) ;
my ( $ rDayOfWeek ) = grep { $ weekdays [ $ _ ] eq $ rDayStr } 0 .. $# weekdays ; # liefert Nr des Wochentages: SU = 0 ... SA = 6
for ( $ ci = - 1 ; $ ci < ( $ count * $ interval ) ; $ ci + + ) {
$ rNewTime += $ interval * 604800 if ( $ ci >= 0 ) ; # Wochenintervall addieren
( $ nbss , $ nbmm , $ nbhh , $ bmday , $ bmonth , $ byear , $ rDayOfWeekNew , undef , undef ) = localtime ( $ rNewTime ) ;
( $ nbhh , $ nbmm , $ nbss ) = split ( ":" , $ nbtime ) ;
if ( $ rDayOfWeekNew <= $ rDayOfWeek ) { # Nr aktueller Wochentag <= Sollwochentag
$ rDaysToAddOrSub = $ rDayOfWeek - $ rDayOfWeekNew ;
} else {
$ rDaysToAddOrSub = 7 - $ rDayOfWeekNew + $ rDayOfWeek ;
$ rNewTime -= 604800 ; # eine Woche zurückgehen wenn Korrektur aufaddiert wurde
}
$ rDaysToAddOrSub += ( 7 * ( $ rDayInterval - 1 ) ) ; # addiere Tagesintervall, z.B. 4th Freitag ...
$ rNewTime = SSCal_plusNSeconds ( $ rNewTime , 86400 * $ rDaysToAddOrSub , 1 ) ;
( $ nbss , $ nbmm , $ nbhh , $ bmday , $ bmonth , $ byear , $ ness , $ nemm , $ nehh , $ emday , $ emonth , $ eyear ) = SSCal_DTfromStartandDiff ( $ rNewTime , $ startEndDiff ) ;
$ nbtime = $ nbhh . $ nbmm . $ nbss ;
$ netime = $ nehh . $ nemm . $ ness ;
( $ bi , undef , $ nbdate , $ nbtime , $ nbts ) = SSCal_explodeDateTime ( $ hash , $ byear . $ bmonth . $ bmday . "T" . $ nbtime ) ; # Beginn des Wiederholungsevents
( $ ei , undef , $ nedate , $ netime , $ nets ) = SSCal_explodeDateTime ( $ hash , $ eyear . $ emonth . $ emday . "T" . $ netime ) ; # Ende des Wiederholungsevents
Log3 ( $ name , 5 , "$name - WEEKLY event - Begin: $nbdate $nbtime, End: $nedate $netime" ) ;
if ( defined $ uets && ( $ uets < $ nbts ) ) { # Event Ende (UNTIL) kleiner aktueller Select Start
Log3 ( $ name , 4 , "$name - Ignore WEEKLY event due to UNTIL -> $data->{data}{$key}[$i]{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
Log3 ( $ name , 4 , "$name - Ignore WEEKLY event -> $data->{data}{$key}[$i]{summary} , start: $nbdate $nbtime, end: $nedate $netime" ) ;
$ ignore = 1 ;
$ done = 0 ;
} else {
$ bdate = $ nbdate ? $ nbdate: $ bdate ;
$ btime = $ nbtime ? $ nbtime: $ btime ;
$ bts = $ nbts ? $ nbts: $ bts ;
$ edate = $ nedate ? $ nedate: $ edate ;
$ etime = $ netime ? $ netime: $ etime ;
$ ets = $ nets ? $ nets: $ ets ;
@ row_array = SSCal_writeValuesToArray ( $ name , $ n , $ data - > { data } { $ key } [ $ i ] , $ tz , $ bdate , $ btime , $ bts , $ edate , $ etime , $ ets , \ @ row_array ) ;
$ ignore = 0 ;
$ done = 1 ;
$ n + + ;
next ;
}
last if ( ( defined $ uets && ( $ uets < $ nbts ) ) || $ nbts > $ tend ) ;
}
}
} else {
my ( $ nbhh , $ nbmm , $ nbss , $ nehh , $ nemm , $ ness , $ rDayOfWeekNew , $ rDaysToAddOrSub ) ;
my $ rNewTime = $ bts ;
for ( $ ci = - 1 ; $ ci < ( $ count * $ interval ) ; $ ci + + ) {
$ rNewTime += $ interval * 604800 if ( $ ci >= 0 ) ; # Wochenintervall addieren
( $ nbss , $ nbmm , $ nbhh , $ bmday , $ bmonth , $ byear , $ ness , $ nemm , $ nehh , $ emday , $ emonth , $ eyear ) = SSCal_DTfromStartandDiff ( $ rNewTime , $ startEndDiff ) ;
$ nbtime = $ nbhh . $ nbmm . $ nbss ;
$ netime = $ nehh . $ nemm . $ ness ;
( $ bi , undef , $ nbdate , $ nbtime , $ nbts ) = SSCal_explodeDateTime ( $ hash , $ byear . $ bmonth . $ bmday . "T" . $ nbtime ) ; # Beginn des Wiederholungsevents
( $ ei , undef , $ nedate , $ netime , $ nets ) = SSCal_explodeDateTime ( $ hash , $ eyear . $ emonth . $ emday . "T" . $ netime ) ; # Ende des Wiederholungsevents
Log3 ( $ name , 5 , "$name - WEEKLY event - Begin: $nbdate $nbtime, End: $nedate $netime" ) ;
if ( defined $ uets && ( $ uets < $ nbts ) ) { # Event Ende (UNTIL) kleiner aktueller Select Start
Log3 ( $ name , 4 , "$name - Ignore WEEKLY event due to UNTIL -> $data->{data}{$key}[$i]{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
Log3 ( $ name , 4 , "$name - Ignore WEEKLY event -> $data->{data}{$key}[$i]{summary} , start: $nbdate $nbtime, end: $nedate $netime" ) ;
$ ignore = 1 ;
$ done = 0 ;
} else {
$ bdate = $ nbdate ? $ nbdate: $ bdate ;
$ btime = $ nbtime ? $ nbtime: $ btime ;
$ bts = $ nbts ? $ nbts: $ bts ;
$ edate = $ nedate ? $ nedate: $ edate ;
$ etime = $ netime ? $ netime: $ etime ;
$ ets = $ nets ? $ nets: $ ets ;
@ row_array = SSCal_writeValuesToArray ( $ name , $ n , $ data - > { data } { $ key } [ $ i ] , $ tz , $ bdate , $ btime , $ bts , $ edate , $ etime , $ ets , \ @ row_array ) ;
$ ignore = 0 ;
$ done = 1 ;
$ n + + ;
next ;
}
last if ( ( defined $ uets && ( $ uets < $ nbts ) ) || $ nbts > $ tend ) ;
}
}
}
if ( $ freq eq "DAILY" ) { # tägliche Wiederholung
my ( $ nbhh , $ nbmm , $ nbss , $ nehh , $ nemm , $ ness ) ;
for ( $ ci = - 1 ; $ ci < ( $ count * $ interval ) ; $ ci += $ interval ) {
$ bts += 86400 if ( $ ci >= 0 ) ;
( $ nbss , $ nbmm , $ nbhh , $ bmday , $ bmonth , $ byear , $ ness , $ nemm , $ nehh , $ emday , $ emonth , $ eyear ) = SSCal_DTfromStartandDiff ( $ bts , $ startEndDiff ) ;
$ nbtime = $ nbhh . $ nbmm . $ nbss ;
$ netime = $ nehh . $ nemm . $ ness ;
( $ bi , undef , $ nbdate , $ nbtime , $ nbts ) = SSCal_explodeDateTime ( $ hash , $ byear . $ bmonth . $ bmday . "T" . $ nbtime ) ; # Beginn des Wiederholungsevents
( $ ei , undef , $ nedate , $ netime , $ nets ) = SSCal_explodeDateTime ( $ hash , $ eyear . $ emonth . $ emday . "T" . $ netime ) ; # Ende des Wiederholungsevents
Log3 ( $ name , 5 , "$name - DAILY event - Begin: $nbdate $nbtime, End: $nedate $netime" ) ;
if ( defined $ uets && ( $ uets < $ nbts ) ) { # Event Ende (UNTIL) kleiner aktueller Select Start
Log3 ( $ name , 4 , "$name - Ignore DAILY event due to UNTIL -> $data->{data}{$key}[$i]{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
Log3 ( $ name , 4 , "$name - Ignore DAILY event -> $data->{data}{$key}[$i]{summary} , start: $nbdate $nbtime, end: $nedate $netime" ) ;
$ ignore = 1 ;
$ done = 0 ;
} else {
$ bdate = $ nbdate ? $ nbdate: $ bdate ;
$ btime = $ nbtime ? $ nbtime: $ btime ;
$ bts = $ nbts ? $ nbts: $ bts ;
$ edate = $ nedate ? $ nedate: $ edate ;
$ etime = $ netime ? $ netime: $ etime ;
$ ets = $ nets ? $ nets: $ ets ;
@ row_array = SSCal_writeValuesToArray ( $ name , $ n , $ data - > { data } { $ key } [ $ i ] , $ tz , $ bdate , $ btime , $ bts , $ edate , $ etime , $ ets , \ @ row_array ) ;
$ ignore = 0 ;
$ done = 1 ;
$ n + + ;
next ;
}
last if ( ( defined $ uets && ( $ uets < $ nbts ) ) || $ nbts > $ tend ) ;
}
}
}
if ( $ ignore == 1 ) {
$ i + + ;
next ;
}
if ( ! $ done ) { # für Testzwecke mit $ignore = 0 und $done = 0
$ bdate = $ nbdate ? $ nbdate: $ bdate ;
$ btime = $ nbtime ? $ nbtime: $ btime ;
$ bts = $ nbts ? $ nbts: $ bts ;
$ edate = $ nedate ? $ nedate: $ edate ;
$ etime = $ netime ? $ netime: $ etime ;
$ ets = $ nets ? $ nets: $ ets ;
@ row_array = SSCal_writeValuesToArray ( $ name , $ n , $ data - > { data } { $ key } [ $ i ] , $ tz , $ bdate , $ btime , $ bts , $ edate , $ etime , $ ets , \ @ row_array ) ;
}
$ i + + ;
$ n + + ;
}
$ n + + ;
}
# encoding result
my $ rowlist = join ( '_ESC_' , @ row_array ) ;
$ rowlist = encode_base64 ( $ rowlist , "" ) ;
if ( $ am ) { # asynchroner Mode mit BlockingCall
return "$name|$rowlist" ;
} else { # synchoner Modes
return SSCal_createReadings ( "$name|$rowlist" ) ;
}
}
2020-01-31 15:27:30 +00:00
#############################################################################################
# Extrahiert empfangene Tasks aus ToDo-Kalender (Aufgabenliste)
#############################################################################################
sub SSCal_extractToDolist ($) {
my ( $ name ) = @ _ ;
my $ hash = $ defs { $ name } ;
my $ data = delete $ hash - > { eventlist } ;
my $ am = AttrVal ( $ name , "asyncMode" , 0 ) ;
my ( $ val , $ tz , $ td , $ d , $ t , $ uts ) ;
my ( $ bdate , $ btime , $ bts , $ edate , $ etime , $ ets , $ ci , $ numday , $ bi , $ ei , $ startEndDiff ) ;
my ( $ bmday , $ bmonth , $ emday , $ emonth , $ byear , $ eyear , $ nbdate , $ nbtime , $ nbts , $ nedate , $ netime , $ nets , $ ydiff ) ;
my @ row_array ;
my ( undef , $ tstart , $ tend ) = SSCal_timeEdge ( $ name ) ; # Sollstart- und Sollendezeit der Kalenderereignisse ermitteln
my $ datetimestart = FmtDateTime ( $ tstart ) ;
my $ datetimeend = FmtDateTime ( $ tend ) ;
my $ n = 0 ;
foreach my $ key ( keys % { $ data - > { data } } ) {
my $ i = 0 ;
while ( $ data - > { data } { $ key } [ $ i ] ) {
my $ ignore = 0 ;
my $ done = 0 ;
( $ nbdate , $ nedate ) = ( "" , "" ) ;
( $ bi , $ tz , $ bdate , $ btime , $ bts ) = SSCal_explodeDateTime ( $ hash , $ data - > { data } { $ key } [ $ i ] { due } ) ; # Fälligkeit des Tasks (falls gesetzt)
( $ ei , undef , $ edate , $ etime , $ ets ) = SSCal_explodeDateTime ( $ hash , $ data - > { data } { $ key } [ $ i ] { due } ) ; # Ende = Fälligkeit des Tasks (falls gesetzt)
2020-02-09 10:21:37 +00:00
if ( $ bdate && $ edate ) { # nicht jede Aufgabe hat Date / Time gesetzt
2020-01-31 15:27:30 +00:00
$ bdate =~ /(\d{4})-(\d{2})-(\d{2})/ ;
$ bmday = $ 3 ;
$ bmonth = $ 2 ;
$ byear = $ 1 ;
$ nbtime = $ btime ;
$ edate =~ /(\d{4})-(\d{2})-(\d{2})/ ;
$ emday = $ 3 ;
$ emonth = $ 2 ;
$ eyear = $ 1 ;
$ netime = $ etime ;
}
if ( ! $ data - > { data } { $ key } [ $ i ] { is_repeat_evt } ) { # einmaliger Task (momentan gibt es keine Wiederholungstasks)
Log3 ( $ name , 5 , "$name - Single task Begin: $bdate, End: $edate" ) if ( $ bdate && $ edate ) ;
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 {
@ row_array = SSCal_writeValuesToArray ( $ name , $ n , $ data - > { data } { $ key } [ $ i ] , $ tz , $ bdate , $ btime , $ bts , $ edate , $ etime , $ ets , \ @ row_array ) ;
$ ignore = 0 ;
$ done = 1 ;
}
}
if ( $ ignore == 1 ) {
$ i + + ;
next ;
}
if ( ! $ done ) { # für Testzwecke mit $ignore = 0 und $done = 0
$ bdate = $ nbdate ? $ nbdate: $ bdate ;
$ btime = $ nbtime ? $ nbtime: $ btime ;
$ bts = $ nbts ? $ nbts: $ bts ;
$ edate = $ nedate ? $ nedate: $ edate ;
$ etime = $ netime ? $ netime: $ etime ;
$ ets = $ nets ? $ nets: $ ets ;
@ row_array = SSCal_writeValuesToArray ( $ name , $ n , $ data - > { data } { $ key } [ $ i ] , $ tz , $ bdate , $ btime , $ bts , $ edate , $ etime , $ ets , \ @ row_array ) ;
}
$ i + + ;
$ n + + ;
}
$ n + + ;
}
# encoding result
my $ rowlist = join ( '_ESC_' , @ row_array ) ;
$ rowlist = encode_base64 ( $ rowlist , "" ) ;
if ( $ am ) { # asynchroner Mode mit BlockingCall
return "$name|$rowlist" ;
} else { # synchoner Modes
return SSCal_createReadings ( "$name|$rowlist" ) ;
}
}
2020-01-19 14:21:41 +00:00
#############################################################################################
2020-02-14 13:20:59 +00:00
# - füllt zentrales $data{SSCal}{$name}{eventlist} Valuehash
# - erstellt Readings aus $data{SSCal}{$name}{eventlist}
# - ruft Routine auf um zusätzliche Steuerungsevents zu erstellen
2020-01-19 14:21:41 +00:00
#############################################################################################
sub SSCal_createReadings ($) {
my ( $ string ) = @ _ ;
my @ a = split ( "\\|" , $ string ) ;
my $ name = $ a [ 0 ] ;
my $ hash = $ defs { $ name } ;
2020-01-31 15:27:30 +00:00
my $ rowlist = decode_base64 ( $ a [ 1 ] ) if ( $ a [ 1 ] ) ;
2020-01-19 14:21:41 +00:00
2020-02-16 21:48:39 +00:00
my @ abnr ;
2020-01-31 15:27:30 +00:00
if ( $ rowlist ) {
my @ row_array = split ( "_ESC_" , $ rowlist ) ;
# zentrales Datenhash füllen (erzeugt dadurch sortierbare Keys)
foreach my $ row ( @ row_array ) {
chomp $ row ;
my @ r = split ( " " , $ row , 3 ) ;
$ data { SSCal } { $ name } { eventlist } { $ r [ 0 ] } { $ r [ 1 ] } = $ r [ 2 ] ;
}
2020-01-19 14:21:41 +00:00
}
# Readings der Eventliste erstellen
if ( $ data { SSCal } { $ name } { eventlist } ) {
my $ l = length ( keys % { $ data { SSCal } { $ name } { eventlist } } ) ; # Anzahl Stellen des max. Index ermitteln
2020-02-14 13:20:59 +00:00
readingsBeginUpdate ( $ hash ) ;
2020-01-19 14:21:41 +00:00
$ data { SSCal } { $ name } { lstUpdtTs } = $ hash - > { ".updateTime" } ; # letzte Updatezeit speichern (Unix Format)
my $ k = 0 ;
foreach my $ idx ( sort keys % { $ data { SSCal } { $ name } { eventlist } } ) {
2020-02-14 13:20:59 +00:00
my $ idxstr = sprintf ( "%0$l.0f" , $ k ) ; # Blocknummer erstellen
push ( @ abnr , $ idxstr ) ; # Array aller vorhandener Blocknummern erstellen
2020-01-19 14:21:41 +00:00
foreach my $ r ( keys % { $ data { SSCal } { $ name } { eventlist } { $ idx } } ) {
if ( $ r =~ /.*Timestamp$/ ) { # Readings mit Unix Timestamps versteckt erstellen
readingsBulkUpdate ( $ hash , "." . $ idxstr . "_" . $ r , $ data { SSCal } { $ name } { eventlist } { $ idx } { $ r } ) ;
} else {
readingsBulkUpdate ( $ hash , $ idxstr . "_" . $ r , $ data { SSCal } { $ name } { eventlist } { $ idx } { $ r } ) ;
}
}
$ k += 1 ;
}
readingsEndUpdate ( $ hash , 1 ) ;
2020-02-16 21:48:39 +00:00
SSCal_doCompositeEvents ( $ name , \ @ abnr , $ data { SSCal } { $ name } { eventlist } ) ; # spezifische Controlevents erstellen
2020-01-19 14:21:41 +00:00
} else {
SSCal_delReadings ( $ name , 0 ) ; # alle Kalender-Readings löschen
}
SSCal_checkretry ( $ name , 0 ) ;
$ data { SSCal } { $ name } { lastUpdate } = FmtDateTime ( $ data { SSCal } { $ name } { lstUpdtTs } ) if ( $ data { SSCal } { $ name } { lstUpdtTs } ) ;
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdateIfChanged ( $ hash , "Errorcode" , "none" ) ;
readingsBulkUpdateIfChanged ( $ hash , "Error" , "none" ) ;
readingsBulkUpdate ( $ hash , "lastUpdate" , $ data { SSCal } { $ name } { lastUpdate } ) ;
readingsBulkUpdate ( $ hash , "state" , "done" ) ;
readingsEndUpdate ( $ hash , 1 ) ;
SSCal_delReadings ( $ name , 1 ) if ( $ data { SSCal } { $ name } { lstUpdtTs } ) ; # Readings löschen wenn Timestamp nicht "lastUpdate"
2020-02-16 21:48:39 +00:00
if ( AttrVal ( $ name , "createATDevs" , 0 ) ) {
SSCal_createATdevices ( $ name , \ @ abnr , $ data { SSCal } { $ name } { eventlist } ) ; # automatisch at-Devics mit FHEM/Perl-Kommandos erstellen
}
2020-01-19 14:21:41 +00:00
return ;
}
2020-02-14 13:20:59 +00:00
#############################################################################################
# erstellt zusätzliche Steuerungsevents um einfach per Notify
# Gerätesteuerungen aus Kalender einträgen zu generieren
#
# $abnr - Referenz zum Array aller vorhandener Blocknummern
# $evref - Referenz zum zentralen Valuehash ($data{SSCal}{$name}{eventlist})
#
#############################################################################################
sub SSCal_doCompositeEvents ($$$) {
my ( $ name , $ abnr , $ evref ) = @ _ ;
my $ hash = $ defs { $ name } ;
2020-02-17 19:03:52 +00:00
my ( $ desc , $ begin , $ status , $ isrepeat , $ id , $ event ) ;
2020-02-14 13:20:59 +00:00
foreach my $ bnr ( @ { $ abnr } ) {
2020-02-17 19:03:52 +00:00
$ desc = ReadingsVal ( $ name , $ bnr . "_03_Description" , "" ) ;
$ begin = ReadingsVal ( $ name , $ bnr . "_05_Begin" , "" ) ;
$ status = ReadingsVal ( $ name , $ bnr . "_17_Status" , "" ) ;
$ isrepeat = ReadingsVal ( $ name , $ bnr . "_55_isRepeatEvt" , 0 ) ;
$ id = ReadingsVal ( $ name , $ bnr . "_98_EventId" , "" ) ;
2020-02-14 13:20:59 +00:00
$ begin =~ s/\s/T/ ; # Formatierung nach ISO8601 (YYYY-MM-DDTHH:MM:SS) für at-Devices
if ( $ begin ) { # einen Composite-Event erstellen wenn Beginnzeit gesetzt ist
2020-02-17 19:03:52 +00:00
$ event = "composite: $id $isrepeat $begin $status $desc" ;
2020-02-14 13:20:59 +00:00
CommandTrigger ( undef , "$name $event" ) ;
}
}
return ;
}
2020-02-16 21:48:39 +00:00
#############################################################################################
# erstellt automatisch AT-Devices aus Kalendereinträgen die FHEM-Befehle oder
2020-02-17 19:03:52 +00:00
# Perl-Routinen in "Description" enthalten.
2020-02-16 21:48:39 +00:00
# FHEM-Befehle sind in { } und Perl-Routinen in {{ }} einzufassen.
#
# $abnr - Referenz zum Array aller vorhandener Blocknummern
# $evref - Referenz zum zentralen Valuehash ($data{SSCal}{$name}{eventlist})
#
#############################################################################################
sub SSCal_createATdevices ($$$) {
my ( $ name , $ abnr , $ evref ) = @ _ ;
my $ hash = $ defs { $ name } ;
2020-02-17 19:03:52 +00:00
my ( $ desc , $ begin , $ status , $ isrepeat , $ id , @ devs , $ err , $ summary , $ location ) ;
2020-02-16 21:48:39 +00:00
2020-02-17 21:26:20 +00:00
my $ room = AttrVal ( $ name , "room" , "" ) ;
my $ assoc = "" ;
readingsDelete ( $ hash , ".associatedWith" ) ; # Deviceassoziationen löschen
2020-02-16 21:48:39 +00:00
2020-02-17 20:00:18 +00:00
@ devs = devspec2array ( "TYPE=at:FILTER=NAME=SSCal.$name.*" ) ;
foreach ( @ devs ) {
next if ( ! $ defs { $ _ } ) ;
Log3 ( $ name , 4 , "$name - delete device: $_" ) ;
CommandDelete ( undef , $ _ ) ;
}
2020-02-16 21:48:39 +00:00
foreach my $ bnr ( @ { $ abnr } ) {
$ summary = ReadingsVal ( $ name , $ bnr . "_01_Summary" , "" ) ;
2020-02-17 19:03:52 +00:00
$ desc = ReadingsVal ( $ name , $ bnr . "_03_Description" , "" ) ;
2020-02-16 21:48:39 +00:00
$ begin = ReadingsVal ( $ name , $ bnr . "_05_Begin" , "" ) ;
$ status = ReadingsVal ( $ name , $ bnr . "_17_Status" , "" ) ;
2020-02-17 19:03:52 +00:00
$ location = ReadingsVal ( $ name , $ bnr . "_35_Location" , $ room ) ; # Location wird als room gesetzt
$ id = ReadingsVal ( $ name , $ bnr . "_98_EventId" , "" ) ;
2020-02-16 21:48:39 +00:00
2020-02-17 19:03:52 +00:00
if ( $ begin && $ status =~ /upcoming|alarmed/ && $ desc =~ /^\s*\{(.*)\}\s*$/ ) { # ein at-Device erstellen wenn Voraussetzungen erfüllt
2020-02-16 21:48:39 +00:00
my $ cmd = $ 1 ;
2020-02-17 19:03:52 +00:00
$ begin =~ s/\s/T/ ; # Formatierung nach ISO8601 (YYYY-MM-DDTHH:MM:SS) für at-Devices
2020-02-16 21:48:39 +00:00
my $ ao = $ begin ;
$ ao =~ s/[-:]//g ;
2020-02-17 21:26:20 +00:00
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" ) ;
2020-02-17 19:03:52 +00:00
if ( $ err ) {
2020-02-17 21:26:20 +00:00
Log3 ( $ name , 1 , "$name - Error during create \"$atn\": $err" ) ;
2020-02-17 19:03:52 +00:00
} else {
2020-02-17 21:26:20 +00:00
CommandSetReading ( undef , "$atn .associatedWith $name" ) ;
CommandAttr ( undef , "$atn room $location" ) ;
2020-02-17 21:39:54 +00:00
CommandAttr ( undef , "$atn comment $summary - created automatically by SSCal \"$name\" " ) ;
2020-02-17 21:26:20 +00:00
$ assoc . = " $atn" ;
2020-02-17 19:03:52 +00:00
}
2020-02-16 21:48:39 +00:00
}
}
2020-02-17 21:26:20 +00:00
CommandSetReading ( undef , "$name .associatedWith $assoc" ) ;
2020-02-16 21:48:39 +00:00
return ;
}
2020-01-19 14:21:41 +00:00
####################################################################################################
# Abbruchroutine BlockingCall
####################################################################################################
sub SSCal_blockingTimeout (@) {
my ( $ hash , $ cause ) = @ _ ;
my $ name = $ hash - > { NAME } ;
$ cause = $ cause ? $ cause: "Timeout: process terminated" ;
Log3 ( $ name , 1 , "$name -> BlockingCall $hash->{HELPER}{RUNNING_PID}{fn} pid:$hash->{HELPER}{RUNNING_PID}{pid} $cause" ) ;
SSCal_checkretry ( $ name , 0 ) ;
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdateIfChanged ( $ hash , "Error" , $ cause ) ;
readingsBulkUpdateIfChanged ( $ hash , "Errorcode" , "none" ) ;
readingsBulkUpdate ( $ hash , "state" , "Error" ) ;
readingsEndUpdate ( $ hash , 1 ) ;
delete ( $ hash - > { HELPER } { RUNNING_PID } ) ;
return ;
}
#############################################################################################
# liefert aus Unix Timestamp Beginn $bts und einer Differenz (Sekunden) das Beginn und
# Endedatum in der Form:
# Beginn: SS,MM,HH,Tag(01-31),Monat(01-12),Jahr(YYYY)
# Ende: SS,MM,HH,Tag(01-31),Monat(01-12),Jahr(YYYY)
#############################################################################################
sub SSCal_DTfromStartandDiff ($$) {
my ( $ bts , $ diff ) = @ _ ;
my ( $ nbss , $ nbmm , $ nbhh , $ bmday , $ bmonth , $ byear , $ bWday , $ bYday , $ bisdst ) ;
my ( $ ness , $ nemm , $ nehh , $ emday , $ emonth , $ eyear , $ eWday , $ eYday , $ eisdst ) ;
( $ nbss , $ nbmm , $ nbhh , $ bmday , $ bmonth , $ byear , $ bWday , $ bYday , $ bisdst ) = localtime ( $ bts ) ;
$ nbss = sprintf ( "%02d" , $ nbss ) ;
$ nbmm = sprintf ( "%02d" , $ nbmm ) ;
$ nbhh = sprintf ( "%02d" , $ nbhh ) ;
$ bmday = sprintf ( "%02d" , $ bmday ) ;
$ bmonth = sprintf ( "%02d" , $ bmonth + 1 ) ;
$ byear += 1900 ;
( $ ness , $ nemm , $ nehh , $ emday , $ emonth , $ eyear , $ eWday , $ eYday , $ eisdst ) = localtime ( $ bts + $ diff ) ;
$ ness = sprintf ( "%02d" , $ ness ) ;
$ nemm = sprintf ( "%02d" , $ nemm ) ;
$ nehh = sprintf ( "%02d" , $ nehh ) ;
$ emday = sprintf ( "%02d" , $ emday ) ;
$ emonth = sprintf ( "%02d" , $ emonth + 1 ) ;
$ eyear += 1900 ;
return ( $ nbss , $ nbmm , $ nbhh , $ bmday , $ bmonth , $ byear , $ ness , $ nemm , $ nehh , $ emday , $ emonth , $ eyear ) ;
}
#############################################################################################
# schreibe Key/Value Pairs in zentrales Valuearray zur Readingerstellung
# $n = Zusatz f. lfd. Nr. zur Unterscheidung exakt
# zeitgleicher Events
# $vh = Referenz zum Kalenderdatenhash
#
# Ergebisarray Aufbau:
# 0 1 2
# (Index aus BeginTimestamp+lfNr) , (Blockindex_Reading) , (Wert)
2020-02-01 13:38:57 +00:00
#
2020-01-19 14:21:41 +00:00
#############################################################################################
sub SSCal_writeValuesToArray ($$$$$$$$$$$) {
my ( $ name , $ n , $ vh , $ tz , $ bdate , $ btime , $ bts , $ edate , $ etime , $ ets , $ aref ) = @ _ ;
my @ row_array = @ { $ aref } ;
my $ hash = $ defs { $ name } ;
2020-02-11 23:05:45 +00:00
my $ lang = AttrVal ( "global" , "language" , "EN" ) ;
2020-02-12 16:28:05 +00:00
my $ ts = time ( ) ; # Istzeit Timestamp
my $ om = $ hash - > { OPMODE } ; # aktuelle Operation Mode
2020-02-01 13:38:57 +00:00
my $ status = "initialized" ;
2020-02-11 23:05:45 +00:00
my ( $ val , $ uts , $ td , $ dleft , $ bWday ) ;
2020-01-19 14:21:41 +00:00
my ( $ upcoming , $ alarmed , $ started , $ ended ) = ( 0 , 0 , 0 , 0 ) ;
2020-02-12 16:28:05 +00:00
$ upcoming = SSCal_isUpcoming ( $ ts , 0 , $ bts ) ; # initiales upcoming
2020-01-19 14:21:41 +00:00
$ started = SSCal_isStarted ( $ ts , $ bts , $ ets ) ;
$ ended = SSCal_isEnded ( $ ts , $ ets ) ;
2020-02-07 14:59:41 +00:00
if ( $ bdate && $ btime ) {
2020-02-09 10:21:37 +00:00
push ( @ row_array , $ bts + $ n . " 05_Begin " . $ bdate . " " . $ btime . "\n" ) ;
2020-02-12 16:28:05 +00:00
my ( $ ny , $ nm , $ nd , undef ) = split ( /[ -]/ , TimeNow ( ) ) ; # Datum Jetzt
my ( $ by , $ bm , $ bd ) = split ( "-" , $ bdate ) ; # Beginn Datum
2020-02-07 14:59:41 +00:00
my $ ntimes = fhemTimeLocal ( 00 , 00 , 00 , $ nd , $ nm - 1 , $ ny - 1900 ) ;
my $ btimes = fhemTimeLocal ( 00 , 00 , 00 , $ bd , $ bm - 1 , $ by - 1900 ) ;
if ( $ btimes >= $ ntimes ) {
$ dleft = int ( ( $ btimes - $ ntimes ) / 86400 ) ;
}
2020-02-11 23:05:45 +00:00
my @ days ;
( 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 {
@ days = qw( Sunday Monday Tuesday Wednesday Thursday Friday Saturday ) ;
}
$ bWday = $ days [ $ bWday ] ;
2020-02-07 14:59:41 +00:00
}
2020-02-09 10:21:37 +00:00
push ( @ row_array , $ bts + $ n . " 07_bTimestamp " . $ bts . "\n" ) if ( $ bts ) ;
push ( @ row_array , $ bts + $ n . " 10_End " . $ edate . " " . $ etime . "\n" ) if ( $ edate && $ etime ) ;
push ( @ row_array , $ bts + $ n . " 13_eTimestamp " . $ ets . "\n" ) if ( $ ets ) ;
push ( @ row_array , $ bts + $ n . " 15_Timezone " . $ tz . "\n" ) if ( $ tz ) ;
push ( @ row_array , $ bts + $ n . " 20_daysLeft " . $ dleft . "\n" ) if ( defined $ dleft ) ;
push ( @ row_array , $ bts + $ n . " 25_daysLeftLong " . "in " . $ dleft . " Tagen\n" ) if ( defined $ dleft ) ;
2020-02-12 07:51:27 +00:00
push ( @ row_array , $ bts + $ n . " 30_Weekday " . $ bWday . "\n" ) if ( defined $ bWday ) ;
2020-01-19 14:21:41 +00:00
foreach my $ p ( keys % { $ vh } ) {
2020-01-31 15:27:30 +00:00
$ vh - > { $ p } = "" if ( ! defined $ vh - > { $ p } ) ;
2020-02-03 16:13:01 +00:00
$ vh - > { $ p } = SSCal_jboolmap ( $ vh - > { $ p } ) ;
2020-02-01 13:38:57 +00:00
next if ( $ vh - > { $ p } eq "" ) ;
2020-01-19 14:21:41 +00:00
$ val = encode ( "UTF-8" , $ vh - > { $ p } ) ;
2020-01-31 15:27:30 +00:00
push ( @ row_array , $ bts + $ n . " 01_Summary " . $ val . "\n" ) if ( $ p eq "summary" ) ;
2020-02-09 10:21:37 +00:00
push ( @ row_array , $ bts + $ n . " 03_Description " . $ val . "\n" ) if ( $ p eq "description" ) ;
push ( @ row_array , $ bts + $ n . " 35_Location " . $ val . "\n" ) if ( $ p eq "location" ) ;
2020-02-04 14:19:14 +00:00
if ( $ p eq "gps" ) {
my ( $ address , $ lng , $ lat ) = ( "" , "" , "" ) ;
foreach my $ r ( keys % { $ vh - > { gps } } ) {
$ vh - > { $ p } { $ r } = "" if ( ! defined $ vh - > { $ p } { $ r } ) ;
next if ( $ vh - > { $ p } { $ r } eq "" ) ;
if ( $ r eq "address" ) {
2020-02-04 19:35:38 +00:00
$ address = encode ( "UTF-8" , $ vh - > { $ p } { $ r } ) if ( $ vh - > { $ p } { $ r } ) ;
2020-02-04 14:19:14 +00:00
}
if ( $ r eq "gps" ) {
$ lng = encode ( "UTF-8" , $ vh - > { $ p } { $ r } { lng } ) ;
$ lat = encode ( "UTF-8" , $ vh - > { $ p } { $ r } { lat } ) ;
}
}
2020-02-09 10:21:37 +00:00
push ( @ row_array , $ bts + $ n . " 40_gpsAddress " . $ address . "\n" ) ;
2020-02-04 19:35:38 +00:00
$ val = "lat=" . $ lat . ",lng=" . $ lng ;
2020-02-09 10:21:37 +00:00
push ( @ row_array , $ bts + $ n . " 45_gpsCoordinates " . $ val . "\n" ) ;
2020-02-04 14:19:14 +00:00
}
2020-02-09 10:21:37 +00:00
push ( @ row_array , $ bts + $ n . " 50_isAllday " . $ val . "\n" ) if ( $ p eq "is_all_day" ) ;
push ( @ row_array , $ bts + $ n . " 55_isRepeatEvt " . $ val . "\n" ) if ( $ p eq "is_repeat_evt" ) ;
2020-02-01 13:38:57 +00:00
2020-01-31 15:27:30 +00:00
if ( $ p eq "due" ) {
my ( undef , undef , $ duedate , $ duetime , $ duets ) = SSCal_explodeDateTime ( $ hash , $ val ) ;
2020-02-09 10:21:37 +00:00
push ( @ row_array , $ bts + $ n . " 60_dueDateTime " . $ duedate . " " . $ duetime . "\n" ) ;
push ( @ row_array , $ bts + $ n . " 65_dueTimestamp " . $ duets . "\n" ) ;
2020-01-31 15:27:30 +00:00
}
2020-02-01 13:38:57 +00:00
2020-02-09 10:21:37 +00:00
push ( @ row_array , $ bts + $ n . " 85_percentComplete " . $ val . "\n" ) if ( $ p eq "percent_complete" && $ om eq "todolist" ) ;
2020-02-01 13:38:57 +00:00
push ( @ row_array , $ bts + $ n . " 90_calName " . SSCal_getCalFromId ( $ hash , $ val ) . "\n" ) if ( $ p eq "original_cal_id" ) ;
2020-01-19 14:21:41 +00:00
if ( $ p eq "evt_repeat_setting" ) {
foreach my $ r ( keys % { $ vh - > { evt_repeat_setting } } ) {
2020-02-01 13:38:57 +00:00
$ vh - > { $ p } { $ r } = "" if ( ! defined $ vh - > { $ p } { $ r } ) ;
next if ( $ vh - > { $ p } { $ r } eq "" ) ;
$ val = encode ( "UTF-8" , $ vh - > { $ p } { $ r } ) ;
2020-02-09 10:21:37 +00:00
push ( @ row_array , $ bts + $ n . " 70_repeatRule " . $ val . "\n" ) if ( $ r eq "repeat_rule" ) ;
2020-01-19 14:21:41 +00:00
}
}
2020-02-09 10:21:37 +00:00
2020-01-19 14:21:41 +00:00
if ( $ p eq "evt_notify_setting" ) {
2020-01-31 15:27:30 +00:00
my $ l = length ( scalar @ { $ vh - > { evt_notify_setting } } ) ; # Anzahl Stellen (Länge) des aktuellen Arrays
my $ ens = 0 ;
2020-01-19 14:21:41 +00:00
while ( $ vh - > { evt_notify_setting } [ $ ens ] ) {
foreach my $ r ( keys % { $ vh - > { evt_notify_setting } [ $ ens ] } ) {
$ vh - > { $ p } [ $ ens ] { $ r } = "" if ( ! $ vh - > { $ p } [ $ ens ] { $ r } ) ;
$ val = encode ( "UTF-8" , $ vh - > { $ p } [ $ ens ] { $ r } ) ;
if ( $ r eq "time_value" ) { # Erinnerungstermine (Array) relativ zur Beginnzeit ermitteln
( $ uts , $ td ) = SSCal_evtNotTime ( $ name , $ val , $ bts ) ;
2020-02-09 10:21:37 +00:00
push ( @ row_array , $ bts + $ n . " 75_" . sprintf ( "%0$l.0f" , $ ens ) . "_notifyTimestamp " . $ uts . "\n" ) ;
push ( @ row_array , $ bts + $ n . " 80_" . sprintf ( "%0$l.0f" , $ ens ) . "_notifyDateTime " . $ td . "\n" ) ;
2020-01-19 14:21:41 +00:00
$ alarmed = SSCal_isAlarmed ( $ ts , $ uts , $ bts ) if ( ! $ alarmed ) ;
}
}
$ ens + + ;
}
}
2020-02-09 10:21:37 +00:00
push ( @ row_array , $ bts + $ n . " 98_EventId " . $ val . "\n" ) if ( $ p eq "evt_id" ) ;
2020-01-19 14:21:41 +00:00
}
$ status = "upcoming" if ( $ upcoming ) ;
$ status = "alarmed" if ( $ alarmed ) ;
$ status = "started" if ( $ started ) ;
$ status = "ended" if ( $ ended ) ;
2020-02-09 10:21:37 +00:00
push ( @ row_array , $ bts + $ n . " 17_Status " . $ status . "\n" ) ;
2020-01-19 14:21:41 +00:00
push ( @ row_array , $ bts + $ n . " 99_---------------------- " . "--------------------------------------------------------------------" . "\n" ) ;
return @ row_array ;
}
#############################################################################################
# Ist Event bevorstehend ?
# Rückkehrwert 1 wenn aktueller Timestamp $ts vor Alarmzeit $ats und vor Startzeit $bts,
# sonst 0
#############################################################################################
sub SSCal_isUpcoming ($$$) {
my ( $ ts , $ ats , $ bts ) = @ _ ;
if ( $ ats ) {
return $ ts < $ ats ? 1 : 0 ;
} else {
return $ ts < $ bts ? 1 : 0 ;
}
}
#############################################################################################
# Ist Event Alarmzeit erreicht ?
# Rückkehrwert 1 wenn aktueller Timestamp $ts zwischen Alarmzeit $ats und Startzeit $bts,
# sonst 0
#############################################################################################
sub SSCal_isAlarmed ($$$) {
my ( $ ts , $ ats , $ bts ) = @ _ ;
return $ ats ? ( ( $ ats <= $ ts && $ ts < $ bts ) ? 1 : 0 ) : 0 ;
}
#############################################################################################
# Ist Event gestartet ?
# Rückkehrwert 1 wenn aktueller Timestamp $ts zwischen Startzeit $bts und Endezeit $ets,
# sonst 0
#############################################################################################
sub SSCal_isStarted ($$$) {
my ( $ ts , $ bts , $ ets ) = @ _ ;
return 0 unless ( $ bts ) ;
return 0 if ( $ ts < $ bts ) ;
if ( defined ( $ ets ) ) {
return 0 if ( $ ts >= $ ets ) ;
}
return 1 ;
}
#############################################################################################
# Ist Event beendet ?
# Rückkehrwert 1 wenn aktueller Timestamp $ts größer Endezeit $ets,
# sonst 0
#############################################################################################
sub SSCal_isEnded ($$) {
my ( $ ts , $ ets ) = @ _ ;
return 0 unless ( $ ets && $ ts ) ;
return $ ets <= $ ts ? 1 : 0 ;
}
#############################################################################################
# check SID
#############################################################################################
sub SSCal_checkSID ($) {
my ( $ name ) = @ _ ;
my $ hash = $ defs { $ name } ;
# SID holen bzw. login
my $ subref = "SSCal_calop" ;
if ( ! $ hash - > { HELPER } { SID } ) {
Log3 ( $ name , 3 , "$name - no session ID found - get new one" ) ;
SSCal_login ( $ hash , $ subref ) ;
return ;
}
return SSCal_calop ( $ name ) ;
}
####################################################################################
# Login for SID
####################################################################################
sub SSCal_login ($$) {
my ( $ hash , $ fret ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ serveraddr = $ hash - > { ADDR } ;
my $ serverport = $ hash - > { PORT } ;
my $ proto = $ hash - > { PROT } ;
my $ apiauth = $ SSCal_api { APIAUTH } { NAME } ;
my $ apiauthpath = $ SSCal_api { APIAUTH } { PATH } ;
my $ apiauthmaxver = $ SSCal_api { 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 ) = SSCal_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 <username> <password>\"" ) ;
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 = 60 if ( $ timeout < 60 ) ;
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 = > \ & SSCal_login_return
} ;
HttpUtils_NonblockingGet ( $ param ) ;
}
sub SSCal_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 SSCal_login ( $ hash , $ fret ) ;
} elsif ( $ myjson ne "" ) {
# Evaluiere ob Daten im JSON-Format empfangen wurden
( $ hash , $ success ) = SSCal_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
2020-02-01 19:54:01 +00:00
my $ error = SSCal_experrorauth ( $ hash , $ errorcode ) ;
2020-01-19 14:21:41 +00:00
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 SSCal_login ( $ hash , $ fret ) ;
}
}
return SSCal_login ( $ hash , $ fret ) ;
}
###################################################################################
# Funktion logout
###################################################################################
sub SSCal_logout ($) {
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ serveraddr = $ hash - > { ADDR } ;
my $ serverport = $ hash - > { PORT } ;
my $ proto = $ hash - > { PROT } ;
my $ apiauth = $ SSCal_api { APIAUTH } { NAME } ;
my $ apiauthpath = $ SSCal_api { APIAUTH } { PATH } ;
my $ apiauthmaxver = $ SSCal_api { 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 = > \ & SSCal_logout_return
} ;
HttpUtils_NonblockingGet ( $ param ) ;
}
sub SSCal_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 ) = SSCal_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 ) = SSCal_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
2020-02-01 19:54:01 +00:00
$ error = SSCal_experrorauth ( $ hash , $ errorcode ) ;
2020-01-19 14:21:41 +00:00
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 SSCal_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 = SSCal_experror ( $ hash , $ errorcode ) ;
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdateIfChanged ( $ hash , "Errorcode" , $ errorcode ) ;
readingsBulkUpdateIfChanged ( $ hash , "Error" , $ error ) ;
readingsBulkUpdate ( $ hash , "state" , "Error" ) ;
readingsEndUpdate ( $ hash , 1 ) ;
} ;
return ( $ hash , $ success , $ myjson ) ;
}
###############################################################################
# JSON Boolean Test und Mapping
###############################################################################
sub SSCal_jboolmap ($) {
my ( $ bool ) = @ _ ;
if ( JSON:: is_bool ( $ bool ) ) {
$ bool = $ bool ? "true" : "false" ;
}
return $ bool ;
}
##############################################################################
2020-02-01 19:54:01 +00:00
# Auflösung Errorcodes Calendar AUTH API
# Übernahmewerte sind $hash, $errorcode
##############################################################################
sub SSCal_experrorauth ($$) {
my ( $ hash , $ errorcode ) = @ _ ;
my $ device = $ hash - > { NAME } ;
my $ error ;
unless ( exists ( $ SSCal_errauthlist { "$errorcode" } ) ) {
$ error = "Value of errorcode \"$errorcode\" not found." ;
return ( $ error ) ;
}
$ error = $ SSCal_errauthlist { "$errorcode" } ;
return ( $ error ) ;
}
##############################################################################
# Auflösung Errorcodes Calendar API
2020-01-19 14:21:41 +00:00
# Übernahmewerte sind $hash, $errorcode
##############################################################################
sub SSCal_experror ($$) {
my ( $ hash , $ errorcode ) = @ _ ;
my $ device = $ hash - > { NAME } ;
my $ error ;
unless ( exists ( $ SSCal_errlist { "$errorcode" } ) ) {
$ error = "Value of errorcode \"$errorcode\" not found." ;
return ( $ error ) ;
}
$ error = $ SSCal_errlist { "$errorcode" } ;
return ( $ error ) ;
}
################################################################
# sortiert eine Liste von Versionsnummern x.x.x
# Schwartzian Transform and the GRT transform
# Übergabe: "asc | desc",<Liste von Versionsnummern>
################################################################
sub SSCal_sortVersion (@) {
my ( $ sseq , @ versions ) = @ _ ;
2020-02-16 21:48:39 +00:00
return "" if ( ! @ versions ) ;
2020-01-19 14:21:41 +00:00
my @ sorted = map { $ _ - > [ 0 ] }
sort { $ a - > [ 1 ] cmp $ b - > [ 1 ] }
map { [ $ _ , pack "C*" , split /\./ ] } @ versions ;
@ sorted = map { join "." , unpack "C*" , $ _ }
sort
map { pack "C*" , split /\./ } @ versions ;
if ( $ sseq eq "desc" ) {
@ sorted = reverse @ sorted ;
}
return @ sorted ;
}
######################################################################################
# credentials speichern
######################################################################################
sub SSCal_setcredentials ($@) {
my ( $ hash , @ credentials ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my ( $ success , $ credstr , $ username , $ passwd , $ index , $ retcode ) ;
my ( @ key , $ len , $ i ) ;
my $ ao = "credentials" ;
2020-01-20 19:06:58 +00:00
$ credstr = encode_base64 ( join ( '!_ESC_!' , @ credentials ) ) ;
2020-01-19 14:21:41 +00:00
# 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 ) = SSCal_getcredentials ( $ hash , 1 , $ ao ) ; # Credentials nach Speicherung lesen und in RAM laden ($boot=1)
}
return ( $ success ) ;
}
######################################################################################
# credentials lesen
######################################################################################
sub SSCal_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
2020-01-20 19:06:58 +00:00
( $ username , $ passwd ) = split ( "!_ESC_!" , decode_base64 ( $ credstr ) ) ;
2020-01-19 14:21:41 +00:00
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 SSCal_trim ($) {
my $ str = shift ;
$ str =~ s/^\s+|\s+$//g ;
return ( $ str ) ;
}
#############################################################################################
# Länge Senedequeue updaten
#############################################################################################
sub SSCal_updQLength ($;$) {
my ( $ hash , $ rst ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ ql = keys % { $ data { SSCal } { $ name } { sendqueue } { entries } } ;
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdate ( $ hash , "QueueLenth" , $ 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
#############################################################################################
sub SSCal_timeEdge ($) {
my ( $ name ) = @ _ ;
my $ hash = $ defs { $ name } ;
my ( $ error , $ t1 , $ t2 ) = ( "" , "" , "" ) ;
my ( $ mday , $ mon , $ year ) ;
my $ t = time ( ) ;
my $ corr = 86400 ; # Korrekturbetrag
my $ cutOlderDays = AttrVal ( $ name , "cutOlderDays" , 5 ) . "d" ;
my $ cutLaterDays = AttrVal ( $ name , "cutLaterDays" , 5 ) . "d" ;
# start of time window
( $ error , $ t1 ) = SSCal_GetSecondsFromTimeSpec ( $ cutOlderDays ) ;
if ( $ error ) {
Log3 $ hash , 2 , "$name: attribute cutOlderDays: $error" ;
return ( $ error , "" , "" ) ;
} 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 ) ;
}
# end of time window
( $ error , $ t2 ) = SSCal_GetSecondsFromTimeSpec ( $ cutLaterDays ) ;
if ( $ error ) {
Log3 $ hash , 2 , "$name: attribute cutLaterDays: $error" ;
return ( $ error , "" , "" ) ;
} 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 ) ;
}
return ( "" , $ t1 , $ t2 ) ;
}
#############################################################################################
# Erinnerungstermin relativ zur Beginnzeit $bts ermitteln
# Alarmformat: 'time_value' => '-P2D'
# 'time_value' => '-PT1H'
# 'time_value' => '-PT5M'
# 'time_value' => 'PT0S'
2020-01-31 15:27:30 +00:00
# 'time_value' => 'PT6H'
# 'time_value' => '-P1DT15H'
2020-01-19 14:21:41 +00:00
#
# Rückgabe: $uts: Unix-Timestamp
# $ts: Timstamp als YYYY-MM-DD HH:MM:SS
#
#############################################################################################
sub SSCal_evtNotTime ($$$) {
my ( $ name , $ tv , $ bts ) = @ _ ;
my $ hash = $ defs { $ name } ;
my ( $ uts , $ ts ) = ( "" , "" ) ;
my ( $ corr ) ;
return ( "" , "" ) if ( ! $ tv || ! $ bts ) ;
if ( $ tv =~ /^-P(\d)+D$/ ) {
2020-01-31 15:27:30 +00:00
$ corr = - 1 * $ 1 * 86400 ;
2020-01-19 14:21:41 +00:00
} elsif ( $ tv =~ /^-PT(\d+)H$/ ) {
2020-01-31 15:27:30 +00:00
$ corr = - 1 * $ 1 * 3600 ;
2020-01-19 14:21:41 +00:00
} elsif ( $ tv =~ /^-PT(\d+)M$/ ) {
2020-01-31 15:27:30 +00:00
$ corr = - 1 * $ 1 * 60 ;
2020-01-19 14:21:41 +00:00
} elsif ( $ tv =~ /^PT(\d+)S$/ ) {
$ corr = $ 1 ;
2020-01-31 15:27:30 +00:00
} elsif ( $ tv =~ /^PT(\d+)M$/ ) {
$ corr = $ 1 * 60 ;
} elsif ( $ tv =~ /^PT(\d+)H$/ ) {
$ corr = $ 1 * 3600 ;
} elsif ( $ tv =~ /^-P(\d)+DT(\d+)H$/ ) {
$ corr = - 1 * ( $ 1 * 86400 + $ 2 * 3600 ) ;
2020-01-19 14:21:41 +00:00
}
if ( defined $ corr ) {
2020-01-31 15:27:30 +00:00
$ uts = $ bts + $ corr ;
2020-01-19 14:21:41 +00:00
$ ts = FmtDateTime ( $ uts ) ;
}
return ( $ uts , $ ts ) ;
}
#############################################################################################
# Unix timestamp aus Zeitdifferenz berechnen
#############################################################################################
sub SSCal_GetSecondsFromTimeSpec ($) {
my ( $ tspec ) = @ _ ;
# days
if ( $ tspec =~ m/^([0-9]+)d$/ ) {
return ( "" , $ 1 * 86400 ) ;
}
# seconds
if ( $ tspec =~ m/^([0-9]+)s?$/ ) {
return ( "" , $ 1 ) ;
}
# D:HH:MM:SS
if ( $ tspec =~ m/^([0-9]+):([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/ ) {
return ( "" , $ 4 + 60 * ( $ 3 + 60 * ( $ 2 + 24 * $ 1 ) ) ) ;
}
# HH:MM:SS
if ( $ tspec =~ m/^([0-9]+):([0-5][0-9]):([0-5][0-9])$/ ) {
return ( "" , $ 3 + 60 * ( $ 2 + ( 60 * $ 1 ) ) ) ;
}
# HH:MM
if ( $ tspec =~ m/^([0-9]+):([0-5][0-9])$/ ) {
return ( "" , 60 * ( $ 2 + 60 * $ 1 ) ) ;
}
return ( "Wrong time specification $tspec" , undef ) ;
}
#############################################################################################
# Clienthash übernehmen oder zusammenstellen
# Identifikation ob über FHEMWEB ausgelöst oder nicht -> erstellen $hash->CL
#############################################################################################
sub SSCal_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 undef ;
}
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 ;
foreach ( @ 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
################################################################
sub SSCal_getCalFromId ($$) {
my ( $ hash , $ cid ) = @ _ ;
my $ cal = "" ;
$ cid = SSCal_trim ( $ cid ) ;
foreach my $ calname ( keys % { $ hash - > { HELPER } { CALENDARS } } ) {
my $ oid = $ hash - > { HELPER } { CALENDARS } { "$calname" } { id } ;
next if ( ! $ oid ) ;
$ oid = SSCal_trim ( $ oid ) ;
if ( $ oid eq $ cid ) {
$ cal = $ calname ;
last ;
}
}
return $ cal ;
}
################################################################
# addiert Anzahl ($n) Sekunden ($s) zu $t1
################################################################
sub SSCal_plusNSeconds ($$$) {
my ( $ t1 , $ s , $ n ) = @ _ ;
$ n = 1 unless defined ( $ n ) ;
my $ t2 = $ t1 + $ n * $ s ;
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 SSCal_delReadings ($$) {
my ( $ name , $ respts ) = @ _ ;
my ( $ lu , $ rts , $ excl ) ;
$ excl = "Error|Errorcode|QueueLenth|state|nextUpdate" ;
$ excl . = "|lastUpdate" if ( $ respts ) ;
my @ allrds = keys % { $ defs { $ name } { READINGS } } ;
foreach 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)$/ ) ;
}
return ;
}
#############################################################################################
# Datum/Zeit extrahieren
# Eingangsformat: TZID=Europe/Berlin:20191216T133000 oder
# 20191216T133000
# Rückgabe: invalid, Zeitzone, Date(YYYY-MM-DD), Time (HH:MM:SS), UnixTimestamp
# (invalid =1 wenn Datum ungültig, ist nach RFC 5545 diese Wiederholung
# zu ignorieren und auch nicht zu zählen !)
#############################################################################################
2020-02-09 10:21:37 +00:00
sub SSCal_explodeDateTime ($$;$) {
my ( $ hash , $ dt , $ isallday ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my ( $ tz , $ t ) = ( "" , "" ) ;
my ( $ d , $ tstamp ) = ( "" , 0 ) ;
my $ invalid = 0 ;
my $ corrsec = 0 ; # Korrektursekunde
2020-01-31 15:27:30 +00:00
my ( $ sec , $ min , $ hour , $ mday , $ month , $ year ) ;
return ( $ invalid , $ tz , $ d , $ t , $ tstamp ) if ( ! $ dt ) ;
2020-02-09 10:21:37 +00:00
$ corrsec = 1 if ( $ isallday ) ; # wenn Ganztagsevent, Endetermin um 1 Sekunde verkürzen damit Termin am selben Tag 23:59:59 endet (sonst Folgetag 00:00:00)
2020-01-19 14:21:41 +00:00
if ( $ dt =~ /^TZID=.*$/ ) {
( $ tz , $ dt ) = split ( ":" , $ dt ) ;
$ tz = ( split ( "=" , $ tz ) ) [ 1 ] ;
}
( $ d , $ t ) = split ( "T" , $ dt ) ;
$ year = substr ( $ d , 0 , 4 ) ;
$ month = substr ( $ d , 4 , 2 ) ;
$ mday = substr ( $ d , 6 , 2 ) ;
$ d = $ year . "-" . $ month . "-" . $ mday ;
if ( $ t ) {
$ hour = substr ( $ t , 0 , 2 ) ;
$ min = substr ( $ t , 2 , 2 ) ;
$ sec = substr ( $ t , 4 , 2 ) ;
$ t = $ hour . ":" . $ min . ":" . $ sec ;
} else {
$ hour = "00" ;
$ min = "00" ;
$ sec = "00" ;
$ t = "00:00:00" ;
}
unless ( ( $ d . " " . $ t ) =~ /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/ ) {
Log3 ( $ name , 2 , "$name - ERROR - invalid DateTime format for explodeDateTime: $d $t" ) ;
}
2020-02-09 10:21:37 +00:00
if ( $ corrsec ) { # Termin um 1 Sekunde verkürzen damit Termin am selben Tag 23:59:59 endet (sonst Folgetag 00:00:00)
eval { $ tstamp = fhemTimeLocal ( $ sec , $ min , $ hour , $ mday , $ month - 1 , $ year - 1900 ) ; } ;
$ tstamp -= $ corrsec ;
my $ nt = FmtDateTime ( $ tstamp ) ;
$ nt =~ /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/ ;
$ year = $ 1 ;
$ month = $ 2 ;
$ mday = $ 3 ;
$ hour = $ 4 ;
$ min = $ 5 ;
$ sec = $ 6 ;
$ d = $ year . "-" . $ month . "-" . $ mday ;
$ t = $ hour . ":" . $ min . ":" . $ sec ;
}
2020-01-19 14:21:41 +00:00
eval { timelocal ( $ sec , $ min , $ hour , $ mday , $ month - 1 , $ year - 1900 ) ; } ;
if ( $@ ) {
Log3 ( $ name , 3 , "$name - WARNING - invalid format of recurring event: $@. It will be ignored due to RFC 5545 standard." ) ;
$ invalid = 1 ;
}
eval { $ tstamp = fhemTimeLocal ( $ sec , $ min , $ hour , $ mday , $ month - 1 , $ year - 1900 ) ; } ;
return ( $ invalid , $ tz , $ d , $ t , $ tstamp ) ;
}
#############################################################################################
# Versionierungen des Moduls setzen
# Die Verwendung von Meta.pm und Packages wird berücksichtigt
#############################################################################################
sub SSCal_setVersionInfo ($) {
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ v = ( SSCal_sortVersion ( "desc" , keys % SSCal_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: 50_SSCal.pm 20534 2019-11-18 17:50:17Z DS_Starter $ im Kopf komplett! vorhanden )
$ modules { $ type } { META } { x_version } =~ s/1.1.1/$v/g if ( $ modules { $ type } { META } { x_version } =~ /^1.1.1$/ ) ;
} else {
$ modules { $ type } { META } { x_version } = $ v ;
}
return $@ unless ( FHEM::Meta:: SetInternals ( $ hash ) ) ; # FVERSION wird gesetzt ( nur gesetzt wenn $Id: 50_SSCal.pm 20534 2019-11-18 17:50:17Z DS_Starter $ im Kopf komplett! vorhanden )
if ( __PACKAGE__ eq "FHEM::$type" || __PACKAGE__ eq $ type ) {
# es wird mit Packages gearbeitet -> Perl übliche Modulversion setzen
# mit {<Modul>->VERSION()} im FHEMWEB kann Modulversion abgefragt werden
use version 0.77 ; our $ VERSION = FHEM::Meta:: Get ( $ hash , 'version' ) ;
}
} else {
# herkömmliche Modulstruktur
$ hash - > { VERSION } = $ v ;
}
return ;
}
2020-02-02 21:01:54 +00:00
###############################################################################
# JSON Boolean Test und Mapping
###############################################################################
sub SSCal_jboolmap ($) {
my ( $ bool ) = @ _ ;
if ( JSON:: is_bool ( $ bool ) ) {
2020-02-09 15:05:13 +00:00
my $ b = SSCal_boolean ( $ bool ) ;
2020-02-04 21:09:59 +00:00
$ bool = 1 if ( $ b == $ JSON:: true ) ;
$ bool = 0 if ( $ b == $ JSON:: false ) ;
2020-02-02 21:01:54 +00:00
}
return $ bool ;
}
2020-02-09 15:05:13 +00:00
sub SSCal_boolean {
# might be called as method or as function, so pop() to get the last arg
# instead of shift() to get the first
pop ( ) ? $ JSON:: true : $ JSON:: false
}
2020-02-02 18:47:04 +00:00
#############################################################################################
# Kalenderliste als HTML-Tabelle zurückgeben
#############################################################################################
2020-02-08 11:45:46 +00:00
sub SSCal_calAsHtml ($;$) {
my ( $ name , $ FW_wname ) = @ _ ;
my $ hash = $ defs { $ name } ;
my $ lang = AttrVal ( "global" , "language" , "EN" ) ;
my $ mi = AttrVal ( $ name , "tableColumnMap" , "icon" ) ;
2020-02-12 16:28:05 +00:00
my ( $ symbol , $ begin , $ begind , $ begint , $ end , $ endd , $ endt , $ summary , $ location , $ status , $ desc , $ gps , $ gpsa , $ gpsc ) ;
my ( $ di , $ cal , $ completion , $ tz , $ dleft , $ dleftlong , $ weekday , $ edleft , $ id , $ isallday ) ;
2020-02-13 17:45:33 +00:00
my ( $ colSymbolAlign , $ colBeginAlign , $ colEndAlign , $ colDayAlign , $ colDLongAlign , $ colWeekdayAlign , $ colTzAlign , $ colSummaryAlign , $ colDescAlign , $ colStatusAlign , $ colCompAlign , $ colLocAlign , $ colMapAlign , $ colCalAlign , $ colIdAlign ) ;
2020-02-09 10:21:37 +00:00
# alle Readings in Array einlesen
my @ allrds = keys % { $ defs { $ name } { READINGS } } ;
2020-02-02 18:47:04 +00:00
2020-02-08 11:45:46 +00:00
# Sprachsteuerung
2020-02-05 23:12:14 +00:00
my $ de = 0 ;
if ( $ lang eq "DE" ) { $ de = 1 } ;
2020-02-03 14:16:18 +00:00
2020-02-08 11:45:46 +00:00
# Entscheidung ob Tabelle für Small Screen optimiert
my $ small = 0 ;
2020-02-11 09:11:52 +00:00
if ( $ FW_wname && $ hash - > { HELPER } { tableSpecs } { smallScreenStyles } ) { # Aufruf durch FHEMWEB und smallScreen-Eigenschaft gesetzt
2020-02-08 11:45:46 +00:00
my % specs ;
my $ FW_style = AttrVal ( $ FW_wname , "stylesheetPrefix" , "default" ) ;
2020-02-11 09:11:52 +00:00
my @ scspecs = split ( "," , $ hash - > { HELPER } { tableSpecs } { smallScreenStyles } ) ; # Eigenschaft smallScreen in Array lesen
2020-02-08 11:45:46 +00:00
grep { ! $ specs { $ _ } + + } @ scspecs ;
2020-02-11 09:11:52 +00:00
$ small = 1 if ( $ specs { $ FW_style } ) ; # Tabelle für small-Style anpassen
2020-02-08 11:45:46 +00:00
}
# Auswahl der darzustellenden Tabellenfelder
2020-02-03 14:16:18 +00:00
my % seen ;
2020-02-08 09:18:56 +00:00
my @ cof = split ( "," , AttrVal ( $ name , "tableFields" , "Begin,End,Summary,Status,Location" ) ) ;
2020-02-13 12:05:29 +00:00
grep { ! $ seen { $ _ } + + } @ cof ;
# Gestaltung Headerzeile
my $ nohead = 0 ; # Unterdrückung Anzeige Headerzeile: 0 - nein, 1 - Ja
eval { $ nohead = SSCal_evalTableSpecs ( $ hash , $ nohead , $ hash - > { HELPER } { tableSpecs } { cellStyle } { noHeader } , "" , \ @ allrds , "string" ) ; } ;
Log3 ( $ name , 1 , "$name - Syntax error in attribute \"tableSpecs\" near \"cellStyle\": $@" ) if ( $@ ) ;
my $ headalign = "center" ; # Ausrichtung der Headerzeile, default: center
eval { $ headalign = SSCal_evalTableSpecs ( $ hash , $ headalign , $ hash - > { HELPER } { tableSpecs } { cellStyle } { headerAlign } , "" , \ @ allrds , "string" ) ; } ;
Log3 ( $ name , 1 , "$name - Syntax error in attribute \"tableSpecs\" near \"cellStyle\": $@" ) if ( $@ ) ;
$ headalign = "cal" . $ headalign ;
2020-02-02 18:47:04 +00:00
2020-02-13 17:45:33 +00:00
# Tabelle erstellen
my $ out = "<html>" ;
2020-02-05 23:12:14 +00:00
$ out . = "<style>TD.cal {padding-left:10px; padding-right:10px; border-spacing:5px; margin-left:auto; margin-right:auto;}</style>" ;
$ out . = "<style>TD.calbold {font-weight: bold;} </style>" ;
$ out . = "<style>TD.calright {text-align: right;} </style>" ;
2020-02-07 14:59:41 +00:00
$ out . = "<style>TD.calleft {text-align: left;} </style>" ;
2020-02-05 23:12:14 +00:00
$ out . = "<style>TD.calcenter {text-align: center;} </style>" ;
$ out . = "<style>TD.calw150 {width: 150px;} </style>" ;
2020-02-02 18:47:04 +00:00
2020-02-13 17:45:33 +00:00
# Wenn Table class=block alleine steht, zieht es bei manchen Styles die Ausgabe auf 100% Seitenbreite
# lässt sich durch einbetten in eine zusätzliche Table roomoverview eindämmen
$ out . = "<table class='roomoverview'>" ;
$ out . = "<tr>" ;
$ out . = "<td style='text-align: center; padding-left:1px; padding-right:1px; margin:0px'>" ;
2020-02-05 23:12:14 +00:00
$ out . = "<table class='block'>" ;
2020-02-07 21:20:54 +00:00
2020-02-13 17:45:33 +00:00
# Tabellenheader
2020-02-13 12:05:29 +00:00
if ( ! $ nohead ) {
$ out . = "<tr class='odd'>" ;
$ out . = "<td class='cal calbold $headalign'> " . ( ( $ de ) ? 'Symbol' : 'Symbol' ) . " </td>" if ( $ seen { Symbol } ) ;
if ( $ small ) { # nur ein Datumfeld umbrechbar
$ out . = "<td class='cal calbold $headalign'> " . ( ( $ de ) ? 'Start' : 'Begin' ) . " </td>" if ( $ seen { Begin } ) ;
$ out . = "<td class='cal calbold $headalign'> " . ( ( $ de ) ? 'Ende' : 'End' ) . " </td>" if ( $ seen { End } ) ;
} else {
$ out . = "<td class='cal calbold $headalign'> " . ( ( $ de ) ? 'Start' : 'Begin' ) . " </td>" if ( $ seen { Begin } ) ;
$ out . = "<td class='cal calbold $headalign'> " . ( ( $ de ) ? '----' : '----' ) . " </td>" if ( $ seen { Begin } ) ;
$ out . = "<td class='cal calbold $headalign'> " . ( ( $ de ) ? 'Ende' : 'End' ) . " </td>" if ( $ seen { End } ) ;
$ out . = "<td class='cal calbold $headalign'> " . ( ( $ de ) ? '----' : '----' ) . " </td>" if ( $ seen { End } ) ;
}
$ out . = "<td class='cal calbold $headalign'> " . ( ( $ de ) ? 'Resttage' : 'Days left' ) . " </td>" if ( $ seen { DaysLeft } ) ;
$ out . = "<td class='cal calbold $headalign'> " . ( ( $ de ) ? 'Terminziel' : 'Goal' ) . " </td>" if ( $ seen { DaysLeftLong } ) ;
$ out . = "<td class='cal calbold $headalign'> " . ( ( $ de ) ? 'Wochentag' : 'Weekday' ) . " </td>" if ( $ seen { Weekday } ) ;
$ out . = "<td class='cal calbold $headalign'> " . ( ( $ de ) ? 'Zeitzone' : 'Timezone' ) . " </td>" if ( $ seen { Timezone } ) ;
$ out . = "<td class='cal calbold $headalign'> " . ( ( $ de ) ? 'Zusammenfassung' : 'Summary' ) . " </td>" if ( $ seen { Summary } ) ;
$ out . = "<td class='cal calbold $headalign'> " . ( ( $ de ) ? 'Beschreibung' : 'Description' ) . " </td>" if ( $ seen { Description } ) ;
$ out . = "<td class='cal calbold $headalign'> " . ( ( $ de ) ? 'Status' : 'State' ) . " </td>" if ( $ seen { Status } ) ;
$ out . = "<td class='cal calbold $headalign'> " . ( ( $ de ) ? 'Erfüllung (%)' : 'Completion (%)' ) . " </td>" if ( $ seen { Completion } ) ;
$ out . = "<td class='cal calbold $headalign'> " . ( ( $ de ) ? 'Ort' : 'Location' ) . " </td>" if ( $ seen { Location } ) ;
$ out . = "<td class='cal calbold $headalign'> " . ( ( $ de ) ? 'Karte' : 'Map' ) . " </td>" if ( $ seen { Map } ) ;
$ out . = "<td class='cal calbold $headalign'> " . ( ( $ de ) ? 'Kalender' : 'Calendar' ) . " </td>" if ( $ seen { Calendar } ) ;
$ out . = "<td class='cal calbold $headalign'> " . ( ( $ de ) ? 'ID' : 'ID' ) . " </td>" if ( $ seen { EventId } ) ;
$ out . = "</tr>" ;
2020-02-07 21:20:54 +00:00
}
2020-02-02 18:47:04 +00:00
my $ maxbnr ;
foreach my $ key ( keys % { $ defs { $ name } { READINGS } } ) {
next if $ key !~ /^(\d+)_\d+_EventId$/ ;
$ maxbnr = $ 1 if ( ! $ maxbnr || $ 1 > $ maxbnr ) ;
}
2020-02-11 09:11:52 +00:00
2020-02-04 20:19:15 +00:00
return "" if ( ! defined $ maxbnr ) ;
2020-02-11 09:11:52 +00:00
my $ l = length ( $ maxbnr ) ;
2020-02-02 18:47:04 +00:00
my $ k ;
for ( $ k = 0 ; $ k <= $ maxbnr ; $ k + + ) {
2020-02-11 09:11:52 +00:00
my $ bnr = sprintf ( "%0$l.0f" , $ k ) ; # Prestring erstellen
last if ( ! ReadingsVal ( $ name , $ bnr . "_98_EventId" , "" ) ) ; # keine Ausgabe wenn es keine EventId mit Blocknummer 0 gibt -> kein Event/Aufgabe vorhanden
2020-02-02 18:47:04 +00:00
2020-02-07 14:59:41 +00:00
( $ begind , $ begint , $ endd , $ endt , $ gps ) = ( "" , "" , "" , "" , "" ) ;
2020-02-13 17:45:33 +00:00
# Readings auslesen
2020-02-11 09:11:52 +00:00
$ summary = ReadingsVal ( $ name , $ bnr . "_01_Summary" , "" ) ;
$ desc = ReadingsVal ( $ name , $ bnr . "_03_Description" , "" ) ;
$ begin = ReadingsVal ( $ name , $ bnr . "_05_Begin" , "" ) ;
$ end = ReadingsVal ( $ name , $ bnr . "_10_End" , "" ) ;
$ tz = ReadingsVal ( $ name , $ bnr . "_15_Timezone" , "" ) ;
$ status = ReadingsVal ( $ name , $ bnr . "_17_Status" , "" ) ;
$ dleft = ReadingsVal ( $ name , $ bnr . "_20_daysLeft" , "" ) ;
2020-02-11 23:05:45 +00:00
$ dleftlong = ReadingsVal ( $ name , $ bnr . "_25_daysLeftLong" , "" ) ;
2020-02-12 07:51:27 +00:00
$ weekday = ReadingsVal ( $ name , $ bnr . "_30_Weekday" , "" ) ;
2020-02-11 09:11:52 +00:00
$ location = ReadingsVal ( $ name , $ bnr . "_35_Location" , "" ) ;
$ gpsa = ReadingsVal ( $ name , $ bnr . "_40_gpsAddress" , "" ) ;
$ gpsc = ReadingsVal ( $ name , $ bnr . "_45_gpsCoordinates" , "" ) ;
$ completion = ReadingsVal ( $ name , $ bnr . "_85_percentComplete" , "" ) ;
$ cal = ReadingsVal ( $ name , $ bnr . "_90_calName" , "" ) ;
$ id = ReadingsVal ( $ name , $ bnr . "_98_EventId" , "" ) ;
$ isallday = ReadingsVal ( $ name , $ bnr . "_50_isAllday" , "" ) ;
2020-02-02 18:47:04 +00:00
2020-02-04 20:19:15 +00:00
if ( $ gpsc ) {
2020-02-07 14:59:41 +00:00
my $ micon ;
if ( $ mi eq "icon" ) {
2020-02-11 09:11:52 +00:00
# Karten-Icon auswählen
2020-02-13 12:05:29 +00:00
$ di = "it_i-net" ;
eval { $ micon = SSCal_evalTableSpecs ( $ hash , $ di , $ hash - > { HELPER } { tableSpecs } { columnMapIcon } , $ bnr , \ @ allrds , "image" ) ; } ;
Log3 ( $ name , 1 , "$name - Syntax error in attribute \"tableSpecs\" near \"columnMapIcon\": $@" ) if ( $@ ) ;
2020-02-11 23:05:45 +00:00
} elsif ( $ mi eq "data" ) {
2020-02-07 14:59:41 +00:00
$ micon = join ( " " , split ( "," , $ gpsc ) ) ;
} elsif ( $ mi eq "text" ) {
2020-02-11 09:11:52 +00:00
# Karten-Text auswählen
2020-02-13 12:05:29 +00:00
my $ dt = "link" ;
eval { $ micon = SSCal_evalTableSpecs ( $ hash , $ dt , $ hash - > { HELPER } { tableSpecs } { columnMapText } , $ bnr , \ @ allrds , "string" ) ; } ;
Log3 ( $ name , 1 , "$name - Syntax error in attribute \"tableSpecs\" near \"columnMapText\": $@" ) if ( $@ ) ;
2020-02-07 14:59:41 +00:00
} else {
$ micon = "" ;
}
2020-02-04 20:19:15 +00:00
my ( $ lat , $ lng ) = split ( "," , $ gpsc ) ;
2020-02-04 19:35:38 +00:00
$ lat = ( split ( "=" , $ lat ) ) [ 1 ] ;
$ lng = ( split ( "=" , $ lng ) ) [ 1 ] ;
2020-02-07 14:59:41 +00:00
2020-02-11 09:11:52 +00:00
# Kartenanbieter auswählen
2020-02-13 12:05:29 +00:00
my $ up = "GoogleMaps" ;
eval { $ up = SSCal_evalTableSpecs ( $ hash , $ up , $ hash - > { HELPER } { tableSpecs } { columnMapProvider } , $ bnr , \ @ allrds , "string" ) ; } ;
Log3 ( $ name , 1 , "$name - Syntax error in attribute \"tableSpecs\" near \"columnMapProvider\": $@" ) if ( $@ ) ;
2020-02-07 14:59:41 +00:00
if ( $ up eq "GoogleMaps" ) { # Kartenprovider: Google Maps
$ gps = "<a href='https://www.google.de/maps/place/$gpsa/\@$lat,$lng' target='_blank'> $micon </a>" ;
} elsif ( $ up eq "OpenStreetMap" ) {
2020-02-13 12:05:29 +00:00
$ gps = "<a href='https://www.openstreetmap.org/?mlat=$lat&mlon=$lng&zoom=14' target='_blank'> $micon </a>" ; # Kartenprovider: OpenstreetMap
}
}
2020-02-04 15:35:22 +00:00
2020-02-05 23:12:14 +00:00
if ( $ begin ne "" ) { # Datum sprachabhängig konvertieren bzw. heute/morgen setzen
my ( $ ny , $ nm , $ nd , undef ) = split ( /[ -]/ , TimeNow ( ) ) ; # Jetzt
my ( $ by , $ bm , $ bd , $ bt ) = split ( /[ -]/ , $ begin ) ;
my ( $ ey , $ em , $ ed , $ et ) = split ( /[ -]/ , $ end ) ;
my $ ntimes = fhemTimeLocal ( 00 , 00 , 00 , $ nd , $ nm - 1 , $ ny - 1900 ) ;
my $ btimes = fhemTimeLocal ( 00 , 00 , 00 , $ bd , $ bm - 1 , $ by - 1900 ) ;
my $ etimes = fhemTimeLocal ( 00 , 00 , 00 , $ ed , $ em - 1 , $ ey - 1900 ) ;
2020-02-07 14:59:41 +00:00
if ( $ de ) {
$ begind = "$bd.$bm.$by" ;
$ endd = "$ed.$em.$ey" ;
} else {
$ begind = "$by-$bm-$bd" ;
$ endd = "$ey-$em-$ed" ;
}
2020-02-09 18:55:42 +00:00
my ( $ a , $ b , undef ) = split ( ":" , $ bt ) ;
$ begint = "$a:$b" ;
my ( $ c , $ d , undef ) = split ( ":" , $ et ) ;
$ endt = "$c:$d" ;
2020-02-07 14:59:41 +00:00
$ edleft = "" ;
2020-02-09 18:55:42 +00:00
2020-02-07 14:59:41 +00:00
if ( $ etimes >= $ ntimes ) {
$ edleft = int ( ( $ etimes - $ ntimes ) / 86400 ) ;
}
2020-02-05 23:12:14 +00:00
2020-02-09 10:21:37 +00:00
$ begind = ( ( $ de ) ? 'heute ' : 'today ' ) if ( $ dleft eq "0" ) ;
2020-02-07 14:59:41 +00:00
$ endd = ( ( $ de ) ? 'heute ' : 'today ' ) if ( $ edleft eq "0" ) ;
2020-02-09 10:21:37 +00:00
$ begind = ( ( $ de ) ? 'morgen ' : 'tomorrow ' ) if ( $ dleft eq "1" ) ;
2020-02-07 14:59:41 +00:00
$ endd = ( ( $ de ) ? 'morgen ' : 'tomorrow ' ) if ( $ edleft eq "1" ) ;
2020-02-09 18:49:02 +00:00
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 ) {
$ begint = "" ;
$ endt = "" ;
}
}
2020-02-11 09:11:52 +00:00
# Icon für Spalte Resttage spezifizieren
2020-02-13 12:05:29 +00:00
eval { $ dleft = SSCal_evalTableSpecs ( $ hash , $ dleft , $ hash - > { HELPER } { tableSpecs } { columnDaysLeftIcon } , $ bnr , \ @ allrds , "image" ) ; } ;
Log3 ( $ name , 1 , "$name - Syntax error in attribute \"tableSpecs\" near \"columnDaysLeftIcon\": $@" ) if ( $@ ) ;
2020-02-11 23:05:45 +00:00
2020-02-11 09:11:52 +00:00
# Icon für Spalte Status spezifizieren
2020-02-13 12:05:29 +00:00
eval { $ status = SSCal_evalTableSpecs ( $ hash , $ status , $ hash - > { HELPER } { tableSpecs } { columnStateIcon } , $ bnr , \ @ allrds , "image" ) ; } ;
Log3 ( $ name , 1 , "$name - Syntax error in attribute \"tableSpecs\" near \"columnStateIcon\": $@" ) if ( $@ ) ;
2020-02-12 16:28:05 +00:00
# Icon für Spalte "Symbol" bestimmen
2020-02-13 12:05:29 +00:00
$ di = ( $ hash - > { MODEL } eq "Diary" ) ? "time_calendar" : "time_note" ;
eval { $ symbol = SSCal_evalTableSpecs ( $ hash , $ di , $ hash - > { HELPER } { tableSpecs } { columnSymbolIcon } , $ bnr , \ @ allrds , "image" ) ; } ;
Log3 ( $ name , 1 , "$name - Syntax error in attribute \"tableSpecs\" near \"columnSymbolIcon\": $@" ) if ( $@ ) ;
# Gestaltung Spaltentext
2020-02-13 17:45:33 +00:00
my $ coldefalign = "center" ; # Ausrichtung der Spalte, default: center
eval {
$ coldefalign = "cal" . SSCal_evalTableSpecs ( $ hash , $ coldefalign , $ hash - > { HELPER } { tableSpecs } { cellStyle } { columnAlign } , "" , \ @ allrds , "string" ) ;
$ colSymbolAlign = "cal" . SSCal_evalTableSpecs ( $ hash , $ coldefalign , $ hash - > { HELPER } { tableSpecs } { cellStyle } { columnSymbolAlign } , "" , \ @ allrds , "string" ) ;
$ colBeginAlign = "cal" . SSCal_evalTableSpecs ( $ hash , $ coldefalign , $ hash - > { HELPER } { tableSpecs } { cellStyle } { columnBeginAlign } , "" , \ @ allrds , "string" ) ;
$ colEndAlign = "cal" . SSCal_evalTableSpecs ( $ hash , $ coldefalign , $ hash - > { HELPER } { tableSpecs } { cellStyle } { columnEndAlign } , "" , \ @ allrds , "string" ) ;
$ colDayAlign = "cal" . SSCal_evalTableSpecs ( $ hash , $ coldefalign , $ hash - > { HELPER } { tableSpecs } { cellStyle } { columnDaysLeftAlign } , "" , \ @ allrds , "string" ) ;
$ colDLongAlign = "cal" . SSCal_evalTableSpecs ( $ hash , $ coldefalign , $ hash - > { HELPER } { tableSpecs } { cellStyle } { columnDaysLeftLongAlign } , "" , \ @ allrds , "string" ) ;
$ colWeekdayAlign = "cal" . SSCal_evalTableSpecs ( $ hash , $ coldefalign , $ hash - > { HELPER } { tableSpecs } { cellStyle } { columnWeekdayAlign } , "" , \ @ allrds , "string" ) ;
$ colTzAlign = "cal" . SSCal_evalTableSpecs ( $ hash , $ coldefalign , $ hash - > { HELPER } { tableSpecs } { cellStyle } { columnTimezoneAlign } , "" , \ @ allrds , "string" ) ;
$ colSummaryAlign = "cal" . SSCal_evalTableSpecs ( $ hash , $ coldefalign , $ hash - > { HELPER } { tableSpecs } { cellStyle } { columnSummaryAlign } , "" , \ @ allrds , "string" ) ;
$ colDescAlign = "cal" . SSCal_evalTableSpecs ( $ hash , $ coldefalign , $ hash - > { HELPER } { tableSpecs } { cellStyle } { columnDescriptionAlign } , "" , \ @ allrds , "string" ) ;
$ colStatusAlign = "cal" . SSCal_evalTableSpecs ( $ hash , $ coldefalign , $ hash - > { HELPER } { tableSpecs } { cellStyle } { columnStatusAlign } , "" , \ @ allrds , "string" ) ;
$ colCompAlign = "cal" . SSCal_evalTableSpecs ( $ hash , $ coldefalign , $ hash - > { HELPER } { tableSpecs } { cellStyle } { columnCompletionAlign } , "" , \ @ allrds , "string" ) ;
$ colLocAlign = "cal" . SSCal_evalTableSpecs ( $ hash , $ coldefalign , $ hash - > { HELPER } { tableSpecs } { cellStyle } { columnLocationAlign } , "" , \ @ allrds , "string" ) ;
$ colMapAlign = "cal" . SSCal_evalTableSpecs ( $ hash , $ coldefalign , $ hash - > { HELPER } { tableSpecs } { cellStyle } { columnMapAlign } , "" , \ @ allrds , "string" ) ;
$ colCalAlign = "cal" . SSCal_evalTableSpecs ( $ hash , $ coldefalign , $ hash - > { HELPER } { tableSpecs } { cellStyle } { columnCalendarAlign } , "" , \ @ allrds , "string" ) ;
$ colIdAlign = "cal" . SSCal_evalTableSpecs ( $ hash , $ coldefalign , $ hash - > { HELPER } { tableSpecs } { cellStyle } { columnEventIdAlign } , "" , \ @ allrds , "string" ) ;
} ;
2020-02-13 12:05:29 +00:00
Log3 ( $ name , 1 , "$name - Syntax error in attribute \"tableSpecs\" near \"cellStyle\": $@" ) if ( $@ ) ;
2020-02-13 17:45:33 +00:00
my $ colalign = $ coldefalign ;
# TabellenBody
2020-02-04 21:09:59 +00:00
$ out . = "<tr class='" . ( $ k & 1 ? "odd" : "even" ) . "'>" ;
2020-02-13 17:45:33 +00:00
$ out . = "<td class='cal $colSymbolAlign' > $symbol </td>" if ( $ seen { Symbol } ) ;
2020-02-07 21:20:54 +00:00
if ( $ small ) {
2020-02-13 17:45:33 +00:00
$ out . = "<td class='cal $colBeginAlign' > " . $ begind . " " . $ begint . "</td>" if ( $ seen { Begin } ) ;
$ out . = "<td class='cal $colEndAlign' > " . $ endd . " " . $ endt . "</td>" if ( $ seen { End } ) ;
2020-02-07 21:20:54 +00:00
} else {
2020-02-13 17:45:33 +00:00
$ out . = "<td class='cal $colBeginAlign' > $begind </td>" if ( $ seen { Begin } ) ;
$ out . = "<td class='cal $colBeginAlign' > $begint </td>" if ( $ seen { Begin } ) ;
$ out . = "<td class='cal $colEndAlign' > $endd </td>" if ( $ seen { End } ) ;
$ out . = "<td class='cal $colEndAlign' > $endt </td>" if ( $ seen { End } ) ;
2020-02-07 21:20:54 +00:00
}
2020-02-13 17:45:33 +00:00
$ out . = "<td class='cal $colDayAlign' > $dleft </td>" if ( $ seen { DaysLeft } ) ;
$ out . = "<td class='cal $colDLongAlign' > $dleftlong </td>" if ( $ seen { DaysLeftLong } ) ;
$ out . = "<td class='cal $colWeekdayAlign'> $weekday </td>" if ( $ seen { Weekday } ) ;
$ out . = "<td class='cal $colTzAlign' > $tz </td>" if ( $ seen { Timezone } ) ;
$ out . = "<td class='cal $colSummaryAlign'> $summary </td>" if ( $ seen { Summary } ) ;
$ out . = "<td class='cal $colDescAlign' > $desc </td>" if ( $ seen { Description } ) ;
$ out . = "<td class='cal $colStatusAlign' > $status </td>" if ( $ seen { Status } ) ;
$ out . = "<td class='cal $colCompAlign' > $completion </td>" if ( $ seen { Completion } ) ;
$ out . = "<td class='cal $colLocAlign' > $location </td>" if ( $ seen { Location } ) ;
$ out . = "<td class='cal $colMapAlign' > $gps </td>" if ( $ seen { Map } ) ;
$ out . = "<td class='cal $colCalAlign' > $cal </td>" if ( $ seen { Calendar } ) ;
$ out . = "<td class='cal $colIdAlign' > $id </td>" if ( $ seen { EventId } ) ;
2020-02-02 18:47:04 +00:00
$ out . = "</tr>" ;
}
2020-02-13 17:45:33 +00:00
$ out . = "</table>" ;
$ out . = "</td>" ;
$ out . = "</tr>" ;
2020-02-02 18:47:04 +00:00
$ out . = "</table>" ;
$ out . = "</html>" ;
return $ out ;
}
2020-02-11 09:11:52 +00:00
######################################################################################
# Evaluiere Eigenschaften von Attribut "tableSpecs"
#
# $hash: Devicehash
# $default: Standardwert - wird wieder zurückgegeben wenn kein Funktionsergebnis
# $specs: Basisschlüssel (z.B. $hash->{HELPER}{tableSpecs}{columnDaysLeft})
2020-02-12 13:54:45 +00:00
# $allreads: Referenz zum ARRAY was alle vorhandenen Readings des Devices enthält
2020-02-11 09:11:52 +00:00
# $bnr: Blocknummer Readings
2020-02-12 13:54:45 +00:00
# $rdtype: erwarteter Datentyp als Rückgabe (image, string)
2020-02-11 09:11:52 +00:00
#
######################################################################################
sub SSCal_evalTableSpecs (@) {
2020-02-12 13:54:45 +00:00
my ( $ hash , $ default , $ specs , $ bnr , $ allrds , $ rdtype ) = @ _ ;
2020-02-11 09:11:52 +00:00
my $ name = $ hash - > { NAME } ;
my $ check ;
2020-02-12 13:54:45 +00:00
$ rdtype = $ rdtype ? $ rdtype : "string" ; # "string" als default Rückgabe Datentyp
2020-02-11 09:11:52 +00:00
# anonymous sub für Abarbeitung Perl-Kommandos
$ check = sub ($) {
my ( $ specs ) = @ _ ;
2020-02-11 23:05:45 +00:00
my $ ret = AnalyzePerlCommand ( undef , $ specs ) ;
2020-02-11 09:11:52 +00:00
return $ ret ;
} ;
no warnings ;
if ( $ specs ) { # Eigenschaft muß Wert haben
my ( $ rn , $ reading , $ uval , $ ui , $ rval ) ;
if ( ref ( $ specs ) eq "ARRAY" ) { # Wenn Schlüssel ein ARRAY enthält
my $ i = 0 ;
while ( $ specs - > [ $ i ] ) {
my $ n = keys % { $ specs - > [ $ i ] } ; # Anzahl Elemente (Entscheidungskriterien) in Hash
foreach my $ k ( keys % { $ specs - > [ $ i ] } ) {
if ( $ k eq "icon" ) {
$ ui = $ specs - > [ $ i ] { $ k } ;
$ n - - ;
next ;
}
2020-02-12 13:54:45 +00:00
foreach my $ r ( @ { $ allrds } ) { # alle vorhandenen Readings evaluieren
2020-02-11 09:11:52 +00:00
if ( $ r =~ m/$k$/ ) {
( undef , $ rn , $ reading ) = split ( "_" , $ r ) ; # Readingnummer evaluieren
$ uval = $ specs - > [ $ i ] { $ k } ; # Vergleichswert
last ;
}
}
$ rval = ReadingsVal ( $ name , $ bnr . "_" . $ rn . "_" . $ reading , "empty" ) ;
$ rval = "\"" . $ rval . "\"" ;
if ( eval ( $ rval . $ uval ) ) {
$ ui = $ specs - > [ $ i ] { icon } ;
$ n - - ;
} else {
$ ui = "" ;
}
}
if ( $ n == 0 && $ ui ) {
2020-02-12 13:54:45 +00:00
$ default = $ ui ; # Defaultwert mit Select ersetzen wenn alle Bedingungen erfüllt
2020-02-11 09:11:52 +00:00
}
$ i + + ;
}
} elsif ( ref ( $ specs ) eq "HASH" ) { # Wenn Schlüssel ein HASH enthält
my $ n = keys % { $ specs } ; # Anzahl Elemente (Entscheidungskriterien) in Hash
foreach my $ k ( keys % { $ specs } ) {
if ( $ k eq "icon" ) {
$ ui = $ specs - > { $ k } ;
$ n - - ;
next ;
}
2020-02-12 13:54:45 +00:00
foreach my $ r ( @ { $ allrds } ) { # alle vorhandenen Readings evaluieren
2020-02-11 09:11:52 +00:00
if ( $ r =~ m/$k$/ ) {
( undef , $ rn , $ reading ) = split ( "_" , $ r ) ; # Readingnummer evaluieren
$ uval = $ specs - > { $ k } ; # Vergleichswert
last ;
}
}
$ rval = ReadingsVal ( $ name , $ bnr . "_" . $ rn . "_" . $ reading , "empty" ) ;
$ rval = "\"" . $ rval . "\"" ;
if ( eval ( $ rval . $ uval ) ) {
$ ui = $ specs - > { icon } ;
$ n - - ;
} else {
$ ui = "" ;
}
}
if ( $ n == 0 && $ ui ) {
2020-02-12 13:54:45 +00:00
$ default = $ ui ; # Defaultwert mit Select ersetzen wenn alle Bedingungen erfüllt
2020-02-11 09:11:52 +00:00
}
} else { # ref Wert der Eigenschaft ist nicht HASH oder ARRAY
if ( $ specs =~ m/^\{.*\}$/s ) { # den Wert als Perl-Funktion ausführen wenn in {}
$ specs =~ s/\$NAME/$name/g ; # Platzhalter $NAME, $BNR ersetzen
$ specs =~ s/\$BNR/$bnr/g ;
$ default = $ check - > ( $ specs ) ;
} else { # einfache key-value Zuweisung
eval ( $ default = $ specs ) ;
}
2020-02-12 13:54:45 +00:00
}
}
if ( $ default && $ rdtype eq "image" ) {
$ default = FW_makeImage ( $ default ) ; # Icon aus "string" errechnen wenn "image" als Rückgabe erwartet wird und $default gesetzt
}
2020-02-11 09:11:52 +00:00
use warnings ;
return $ default ;
}
2020-01-19 14:21:41 +00:00
#############################################################################################
# Hint Hash EN
#############################################################################################
% SSCal_vHintsExt_en = (
) ;
#############################################################################################
# Hint Hash DE
#############################################################################################
% SSCal_vHintsExt_de = (
) ;
1 ;
= pod
= item summary module to integrate Synology Calendar
= item summary_DE Modul zur Integration von Synology Calendar
= begin html
< a name = "SSCal" > </a>
<h3> SSCal </h3>
<ul>
2020-01-19 14:31:45 +00:00
The guide for this module is currently only available in the german < a href = "https://wiki.fhem.de/wiki/SSCal - Integration des Synology Calendar Servers" > Wiki </a> .
2020-01-19 14:21:41 +00:00
</ul>
= end html
= begin html_DE
< a name = "SSCal" > </a>
<h3> SSCal </h3>
<ul>
2020-01-19 14:31:45 +00:00
Die Beschreibung des Moduls ist momentan nur im < a href = "https://wiki.fhem.de/wiki/SSCal - Integration des Synology Calendar Servers" > Wiki </a> vorhanden .
2020-01-19 14:21:41 +00:00
</ul>
= end html_DE
= for : application / json ; q = META . json 57 _SSCal . pm
{
"abstract" : "Integration of Synology Calendar." ,
"x_lang" : {
"de" : {
"abstract" : "Integration des Synology Calendars."
}
} ,
"keywords" : [
"Synology" ,
"Calendar" ,
"Appointments"
] ,
"version" : "v1.1.1" ,
2020-02-12 13:54:45 +00:00
"release_status" : "stable" ,
2020-01-19 14:21:41 +00:00
"author" : [
"Heiko Maaz <heiko.maaz@t-online.de>"
] ,
"x_fhem_maintainer" : [
"DS_Starter"
] ,
"x_fhem_maintainer_github" : [
"nasseeder1"
] ,
"prereqs" : {
"runtime" : {
"requires" : {
"FHEM" : 5.00918799 ,
"perl" : 5.014 ,
2020-02-09 15:05:13 +00:00
"JSON" : 4.020 ,
2020-01-19 14:21:41 +00:00
"Data::Dumper" : 0 ,
"MIME::Base64" : 0 ,
"Time::HiRes" : 0 ,
"HttpUtils" : 0 ,
"Blocking" : 0 ,
"Encode" : 0
} ,
"recommends" : {
"FHEM::Meta" : 0
} ,
"suggests" : {
}
}
} ,
"resources" : {
"x_wiki" : {
"web" : "https://wiki.fhem.de/wiki/SSCal - Integration des Synology Calendar Servers" ,
"title" : "SSCal - Integration des Synology Calendar Servers"
} ,
"repository" : {
"x_dev" : {
"type" : "svn" ,
"url" : "https://svn.fhem.de/trac/browser/trunk/fhem/contrib/DS_Starter" ,
"web" : "https://svn.fhem.de/trac/browser/trunk/fhem/contrib/DS_Starter/57_SSCal.pm" ,
"x_branch" : "dev" ,
"x_filepath" : "fhem/contrib/" ,
"x_raw" : "https://svn.fhem.de/fhem/trunk/fhem/contrib/DS_Starter/57_SSCal.pm"
}
}
}
}
= end : application / json ; q = META . json
= cut