mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-01-31 18:59:33 +00:00
75ab4b1ff5
git-svn-id: https://svn.fhem.de/fhem/trunk@26235 2b470e98-0d58-463d-a4d8-8e2adae1ed80
4961 lines
214 KiB
Perl
4961 lines
214 KiB
Perl
########################################################################################################################
|
|
# $Id: 57_SSCal.pm 24736 2021-07-12 15:43:19Z DS_Starter $
|
|
#########################################################################################################################
|
|
# 57_SSCal.pm
|
|
#
|
|
# (c) 2019 - 2022 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 FHEM::SSCal; ## no critic 'package'
|
|
|
|
use strict;
|
|
use warnings;
|
|
eval "use JSON;1;" or my $SSCalMM = "JSON"; ## no critic 'eval' # Debian: apt-get install libjson-perl
|
|
use Data::Dumper; # Perl Core module
|
|
use GPUtils qw(GP_Import GP_Export); # wird für den Import der FHEM Funktionen aus der fhem.pl benötigt
|
|
use FHEM::SynoModules::API qw(apistatic); # API Modul
|
|
|
|
use FHEM::SynoModules::SMUtils qw( completeAPI
|
|
showAPIinfo
|
|
showModuleInfo
|
|
addSendqueue
|
|
listSendqueue
|
|
purgeSendqueue
|
|
checkSendRetry
|
|
startFunctionDelayed
|
|
evaljson
|
|
getClHash
|
|
delClHash
|
|
delReadings
|
|
setCredentials
|
|
getCredentials
|
|
showStoredCredentials
|
|
setReadingErrorState
|
|
setReadingErrorNone
|
|
login
|
|
logout
|
|
moduleVersion
|
|
updQueueLength
|
|
trim
|
|
jboolmap
|
|
); # Hilfsroutinen Modul
|
|
|
|
use FHEM::SynoModules::ErrCodes qw(expErrors); # Error Code Modul
|
|
use MIME::Base64;
|
|
use POSIX qw(strftime);
|
|
use Time::HiRes qw(gettimeofday);
|
|
use HttpUtils;
|
|
use Encode;
|
|
use utf8;
|
|
use Blocking;
|
|
no if $] >= 5.017011, warnings => 'experimental::smartmatch';
|
|
eval "use FHEM::Meta;1" or my $modMetaAbsent = 1; ## no critic 'eval'
|
|
|
|
# no if $] >= 5.017011, warnings => 'experimental';
|
|
|
|
# Run before module compilation
|
|
BEGIN {
|
|
# Import from main::
|
|
GP_Import(
|
|
qw(
|
|
AnalyzePerlCommand
|
|
asyncOutput
|
|
AttrVal
|
|
BlockingCall
|
|
BlockingKill
|
|
CancelDelayedShutdown
|
|
CommandSet
|
|
CommandAttr
|
|
CommandDelete
|
|
CommandDefine
|
|
CommandGet
|
|
CommandSetReading
|
|
CommandTrigger
|
|
data
|
|
defs
|
|
devspec2array
|
|
fhemTimeLocal
|
|
FmtDateTime
|
|
FmtTime
|
|
FW_makeImage
|
|
getKeyValue
|
|
HttpUtils_NonblockingGet
|
|
init_done
|
|
InternalTimer
|
|
IsDisabled
|
|
Log3
|
|
modules
|
|
readingFnAttributes
|
|
ReadingsVal
|
|
RemoveInternalTimer
|
|
readingsDelete
|
|
readingsBeginUpdate
|
|
readingsBulkUpdate
|
|
readingsBulkUpdateIfChanged
|
|
readingsEndUpdate
|
|
readingsSingleUpdate
|
|
ReadingsTimestamp
|
|
sortTopicNum
|
|
setKeyValue
|
|
TimeNow
|
|
)
|
|
);
|
|
|
|
# Export to main context with different name
|
|
# my $pkg = caller(0);
|
|
# my $main = $pkg;
|
|
# $main =~ s/^(?:.+::)?([^:]+)$/main::$1\_/gx;
|
|
# foreach (@_) {
|
|
# *{ $main . $_ } = *{ $pkg . '::' . $_ };
|
|
# }
|
|
GP_Export(
|
|
qw(
|
|
Initialize
|
|
)
|
|
);
|
|
}
|
|
|
|
# Versions History intern
|
|
my %vNotesIntern = (
|
|
"2.4.10" => "16.07.2022 fix problem recurring MONTHLY appointment by day, ".
|
|
"forum: https://forum.fhem.de/index.php/topic,106963.msg1228098.html#msg1228098 ",
|
|
"2.4.9" => "11.07.2021 set adaption of AUTH for DSM7 compatibility ",
|
|
"2.4.8" => "16.12.2020 accep umlauts in calendar name ",
|
|
"2.4.7" => "08.12.2020 fix handle code recognition in createAtDevices as single line ",
|
|
"2.4.6" => "06.11.2020 bugfix weekly byDay ",
|
|
"2.4.5" => "03.11.2020 fix commandref wiki link ",
|
|
"2.4.4" => "06.10.2020 use addSendqueue from SMUtils, delete local addSendqueue ",
|
|
"2.4.3" => "04.10.2020 use showStoredCredentials from SMUtils ",
|
|
"2.4.2" => "03.10.2020 get from SMUtils: completeAPI showAPIinfo evaljson setReadingErrorState setReadingErrorNone showModuleInfo ".
|
|
"login logout getClHash delClHash trim moduleVersion updQueueLength delReadings checkSendRetry startFunctionDelayed ".
|
|
"minor fix in periodicCall ",
|
|
"2.4.1" => "20.05.2020 new function 'evalTimeAndWrite' ",
|
|
"2.4.0" => "19.05.2020 more changes according to PBP, switch to packages, fix cannot delete (and display in table) EventId of block 0 ",
|
|
"2.3.0" => "25.04.2020 set compatibility to Calendar package 2.3.4-0631, some changes according to PBP ",
|
|
"2.2.3" => "24.03.2020 minor code change ",
|
|
"2.2.2" => "08.03.2020 review commandref ",
|
|
"2.2.1" => "04.03.2020 expand composite event 'compositeBlockNumbers' by 'none' ",
|
|
"2.2.0" => "03.03.2020 new composite event 'compositeBlockNumbers' ",
|
|
"2.1.0" => "01.03.2020 expand composite Event, bugfix API if entry with 'is_all_day' and at first position in 'data' ",
|
|
"2.0.0" => "28.02.2020 check in release ",
|
|
"1.15.0" => "27.02.2020 fix recurrence WEEKLY by DAY, MONTHLY by MONTHDAY and BYDAY, create commandref ",
|
|
"1.14.0" => "23.02.2020 new setter \"calUpdate\" consistent for both models, calEventList and calToDoList are obsolete ",
|
|
"1.13.0" => "22.02.2020 manage recurring entries if one/more of a series entry is deleted or changed and their reminder times ",
|
|
"1.12.0" => "15.02.2020 create At-devices from calendar entries if FHEM-commands or Perl-routines detected in \"Summary\", minor fixes ",
|
|
"1.11.0" => "14.02.2020 new function doCompositeEvents to create Composite Events for special notify use in FHEM ",
|
|
"1.10.0" => "13.02.2020 new key cellStyle for attribute tableSpecs, avoid FHEM crash when are design failures in tableSpecs ",
|
|
"1.9.0" => "11.02.2020 new reading Weekday with localization, more field selection for overview table ",
|
|
"1.8.0" => "09.02.2020 evaluate icons for DaysLeft, Map and State in sub evalTableSpecs , fix no table is shown after FHEM restart ",
|
|
"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, ".
|
|
"tableInDetail, tableInRoom, correct enddate/time if is_all_day incl. bugfix API, function boolean ".
|
|
"to avoid fhem crash if an older JSON module is installed ",
|
|
"1.6.1" => "03.02.2020 rename attributes to \"calOverviewInDetail\",\"calOverviewInRoom\", bugfix of gps extraction ",
|
|
"1.6.0" => "03.02.2020 new attribute \"tableFields\" to show specified fields in calendar overview in detail/room view, ".
|
|
"Model Diary/Tasks defined, periodic call of ToDo-Liists now possible ",
|
|
"1.5.0" => "02.02.2020 new attribute \"calOverviewInDetail\",\"calOverviewInRoom\" to control calendar overview in room or detail view ",
|
|
"1.4.0" => "02.02.2020 get calAsHtml command or use sub calAsHtml(\$name) ",
|
|
"1.3.1" => "01.02.2020 add errauthlist hash for login/logout API error codes ",
|
|
"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 ",
|
|
"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 ",
|
|
"1.1.13" => "20.01.2020 change save and read credentials routine ",
|
|
"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 "
|
|
);
|
|
|
|
my %hset = ( # Hash für Set-Funktion (needcred => 1: Funktion benötigt gesetzte Credentials)
|
|
credentials => { fn => \&_setcredentials, needcred => 0 },
|
|
calUpdate => { fn => \&_setcalUpdate, needcred => 1 },
|
|
deleteEventId => { fn => \&_setdeleteEventId, needcred => 1 },
|
|
eraseReadings => { fn => \&_seteraseReadings, needcred => 0 },
|
|
listSendqueue => { fn => \&listSendqueue, needcred => 0 },
|
|
logout => { fn => \&_setlogout, needcred => 0 },
|
|
purgeSendqueue => { fn => \&purgeSendqueue, needcred => 0 },
|
|
restartSendqueue => { fn => \&_setrestartSendqueue, needcred => 1 },
|
|
cleanCompleteTasks => { fn => \&_setcleanCompleteTasks, needcred => 1 },
|
|
);
|
|
|
|
my %hget = ( # Hash für Get-Funktion (needcred => 1: Funktion benötigt gesetzte Credentials)
|
|
apiInfo => { fn => \&_getapiInfo, needcred => 1 },
|
|
calAsHtml => { fn => \&_getcalAsHtml, needcred => 0 },
|
|
getCalendars => { fn => \&_getgetCalendars, needcred => 1 },
|
|
storedCredentials => { fn => \&_getstoredCredentials, needcred => 1 },
|
|
versionNotes => { fn => \&_getversionNotes, needcred => 0 },
|
|
);
|
|
|
|
my %hvada = ( # Funktionshash Version Adaption
|
|
"a01" => {AUTH => "6", INFO => "1", CAL => "2",
|
|
EVENT => "3", SHARE => "1", TODO => "1" },
|
|
);
|
|
|
|
# Versions History extern
|
|
my %vNotesExtern = (
|
|
"2.4.0" => "19.05.2020 Switched to Perl packages. Use <i>FHEM::SSCal::calAsHtml</i> from now instead of ".
|
|
"<i>SSCal_calAsHtml</i> in definition of weblink devices and own code. ",
|
|
"2.3.0" => "25.04.2020 The module compatibility is set to Synology Calendar package 2.3.4-0631. ",
|
|
"1.0.0" => "18.12.2019 initial "
|
|
);
|
|
|
|
# Hints EN
|
|
my %vHintsExt_en = (
|
|
"1" => "The module implements the Internet Calendaring and Scheduling Core Object Specification (iCalendar) ".
|
|
"according to <a href=\"https://tools.ietf.org/html/rfc5545\">RFC5545</a>. "
|
|
);
|
|
|
|
# Hints DE
|
|
my %vHintsExt_de = (
|
|
"1" => "Das Modul implementiert die Internet Calendaring and Scheduling Core Object Specification (iCalendar) ".
|
|
"gemäß <a href=\"https://tools.ietf.org/html/rfc5545\">RFC5545</a>. "
|
|
);
|
|
|
|
# Standardvariablen
|
|
my $splitstr = "!_ESC_!"; # Split-String zur Übergabe in getCredentials, login & Co.
|
|
my $queueStartFn = "FHEM::SSCal::getApiSites"; # Startfunktion zur Queue-Abarbeitung
|
|
|
|
################################################################
|
|
sub Initialize {
|
|
my ($hash) = @_;
|
|
$hash->{DefFn} = \&Define;
|
|
$hash->{UndefFn} = \&Undef;
|
|
$hash->{DeleteFn} = \&Delete;
|
|
$hash->{SetFn} = \&Set;
|
|
$hash->{GetFn} = \&Get;
|
|
$hash->{AttrFn} = \&Attr;
|
|
$hash->{DelayedShutdownFn} = \&DelayedShutdown;
|
|
|
|
# Darstellung FHEMWEB
|
|
# $hash->{FW_summaryFn} = \&FWsummaryFn;
|
|
$hash->{FW_addDetailToSummary} = 1 ; # zusaetzlich zu der Device-Summary auch eine Neue mit dem Inhalt von DetailFn angezeigt
|
|
$hash->{FW_detailFn} = \&FWdetailFn;
|
|
$hash->{FW_deviceOverview} = 1;
|
|
|
|
$hash->{AttrList} = "asyncMode:1,0 ".
|
|
"createATDevs:1,0 ".
|
|
"cutOlderDays ".
|
|
"cutLaterDays ".
|
|
"disable:1,0 ".
|
|
"tableSpecs:textField-long ".
|
|
"filterCompleteTask:1,2,3 ".
|
|
"filterDueTask:1,2,3 ".
|
|
"interval ".
|
|
"loginRetries:1,2,3,4,5,6,7,8,9,10 ".
|
|
"tableColumnMap:icon,data,text ".
|
|
"showRepeatEvent:true,false ".
|
|
"showPassInLog:1,0 ".
|
|
"tableInDetail:0,1 ".
|
|
"tableInRoom:0,1 ".
|
|
"tableFields:multiple-strict,Symbol,Begin,End,DaysLeft,DaysLeftLong,Weekday,Timezone,Summary,Description,Status,Completion,Location,Map,Calendar,EventId ".
|
|
"timeout ".
|
|
"usedCalendars:--wait#for#Calendar#list-- ".
|
|
$readingFnAttributes;
|
|
|
|
FHEM::Meta::InitMod( __FILE__, $hash ) if(!$modMetaAbsent); # 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)]
|
|
# [1] [2] [3] [4]
|
|
#
|
|
################################################################
|
|
sub 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) {
|
|
return "You need to specify more parameters.\n". "Format: define <name> SSCal <ServerAddress> [Port] [HTTP(S)] [Tasks]";
|
|
}
|
|
|
|
shift @a; shift @a;
|
|
|
|
my $addr = ($a[0] && $a[0] ne "Tasks") ? $a[0] : "";
|
|
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 );
|
|
|
|
$hash->{SERVERADDR} = $addr;
|
|
$hash->{SERVERPORT} = $port;
|
|
$hash->{MODEL} = "Calendar";
|
|
$hash->{PROTOCOL} = $prot;
|
|
$hash->{MODEL} = $model;
|
|
$hash->{RESEND} = "next planned SendQueue start: immediately by next entry";
|
|
$hash->{HELPER}{MODMETAABSENT} = 1 if($modMetaAbsent); # Modul Meta.pm nicht vorhanden
|
|
$hash->{HELPER}{CALFETCHED} = 0; # vorhandene Kalender sind noch nicht abgerufen
|
|
|
|
CommandAttr(undef, "$name room SSCal");
|
|
CommandAttr(undef, "$name event-on-update-reading .*Summary,state");
|
|
|
|
my $params = {
|
|
hash => $hash,
|
|
notes => \%vNotesIntern,
|
|
useAPI => 1,
|
|
useSMUtils => 1,
|
|
useErrCodes => 1
|
|
};
|
|
use version 0.77; our $VERSION = moduleVersion ($params); # Versionsinformationen setzen
|
|
|
|
getCredentials($hash,1,"credentials",$splitstr); # Credentials lesen
|
|
|
|
$data{SSCal}{$name}{sendqueue}{index} = 0; # Index der Sendequeue initialisieren
|
|
|
|
readingsBeginUpdate ($hash);
|
|
readingsBulkUpdateIfChanged ($hash, "Errorcode" , "none");
|
|
readingsBulkUpdateIfChanged ($hash, "Error" , "none");
|
|
readingsBulkUpdateIfChanged ($hash, "QueueLength", 0); # Länge Sendqueue initialisieren
|
|
readingsBulkUpdate ($hash, "nextUpdate" , "undefined"); # Abrufmode initialisieren
|
|
readingsBulkUpdate ($hash, "state" , "Initialized"); # Init state
|
|
readingsEndUpdate ($hash,1);
|
|
|
|
initOnBoot($name); # initiale Routinen nach Start ausführen , verzögerter zufälliger Start
|
|
|
|
return;
|
|
}
|
|
|
|
################################################################
|
|
# 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 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;
|
|
}
|
|
|
|
#######################################################################################################
|
|
# 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 DelayedShutdown {
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
if($hash->{HELPER}{SID}) {
|
|
logout($hash, $data{SSCal}{$name}{calapi}, $splitstr); # 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 Delete {
|
|
my ($hash, $arg) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $index = $hash->{TYPE}."_".$hash->{NAME}."_credentials";
|
|
|
|
setKeyValue($index, undef); # gespeicherte Credentials löschen
|
|
|
|
return;
|
|
}
|
|
|
|
################################################################
|
|
sub Attr { ## no critic 'complexity'
|
|
my ($cmd,$name,$aName,$aVal) = @_;
|
|
my $hash = $defs{$name};
|
|
my $model = $hash->{MODEL};
|
|
my ($do,$val);
|
|
|
|
# $cmd can be "del" or "set"
|
|
# $name is device name
|
|
# aName and aVal are Attribute name and value
|
|
|
|
if ($cmd eq "set") {
|
|
|
|
if ($aName =~ /filterCompleteTask|filterDueTask/x && $model ne "Tasks") {
|
|
return qq{The attribute "$aName" is only valid for devices of MODEL "Tasks" ! Please set this attribute in a device of this MODEL.};
|
|
}
|
|
|
|
if ($aName =~ /showRepeatEvent/x && $model ne "Diary") {
|
|
return qq{The attribute "$aName" is only valid for devices of MODEL "Diary" ! Please set this attribute in a device of this MODEL.};
|
|
}
|
|
|
|
if ($aName =~ /tableSpecs/x) {
|
|
return qq{The attribute "$aName" has wrong syntax. The value must be set into "{ }".} if($aVal !~ m/^\s*?\{.*\}\s*?$/xs);
|
|
}
|
|
|
|
my $attrVal = $aVal;
|
|
|
|
if ($attrVal =~ m/^\{.*\}$/xs && $attrVal =~ m/=>/x) {
|
|
$attrVal =~ s/\@/\\\@/gx;
|
|
$attrVal =~ s/\$/\\\$/gx;
|
|
|
|
my $av = eval $attrVal; ## no critic 'eval'
|
|
if($@) {
|
|
Log3($name, 2, "$name - Error while evaluate: ".$@);
|
|
return $@;
|
|
}
|
|
else {
|
|
$attrVal = $av if(ref($av) eq "HASH");
|
|
}
|
|
}
|
|
$hash->{HELPER}{$aName} = $attrVal;
|
|
}
|
|
else {
|
|
delete $hash->{HELPER}{$aName};
|
|
}
|
|
|
|
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, "FHEM::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/x) {
|
|
unless ($aVal =~ /^\d+$/x) { return qq{The value of $aName is not valid. Use only integers 1-9 !}; }
|
|
}
|
|
if($aName =~ m/interval/x) {
|
|
RemoveInternalTimer($name,"FHEM::SSCal::periodicCall");
|
|
InternalTimer (gettimeofday()+1.0, "FHEM::SSCal::periodicCall", $name, 0);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
#############################################################################################
|
|
# Setter
|
|
#############################################################################################
|
|
sub Set {
|
|
my ($hash, @a) = @_;
|
|
return "\"set X\" needs at least an argument" if ( @a < 2 );
|
|
my $name = $a[0];
|
|
my $opt = $a[1];
|
|
my $prop = $a[2];
|
|
my $prop1 = $a[3];
|
|
|
|
my $model = $hash->{MODEL};
|
|
|
|
my ($success,$setlist);
|
|
|
|
return if(IsDisabled($name));
|
|
|
|
my $idxlist = join(",", sort keys %{$data{SSCal}{$name}{sendqueue}{entries}});
|
|
|
|
# alle aktuell angezeigten Event Id's ermitteln
|
|
my (@idarray,$evids);
|
|
for my $key (keys %{$defs{$name}{READINGS}}) {
|
|
next if $key !~ /_EventId$/x;
|
|
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);
|
|
}
|
|
|
|
$setlist = "Unknown argument $opt, choose one of ";
|
|
|
|
if(!$hash->{CREDENTIALS}) { # initiale setlist für neue Devices
|
|
$setlist .= "credentials ";
|
|
}
|
|
|
|
if($hash->{CREDENTIALS}) { # Model Terminkalender & Aufgabenliste
|
|
$setlist .= "calUpdate ".
|
|
"credentials ".
|
|
"eraseReadings:noArg ".
|
|
"listSendqueue:noArg ".
|
|
"logout:noArg ".
|
|
"restartSendqueue:noArg ".
|
|
($evids ? "deleteEventId:$evids " : "deleteEventId:noArg " ).
|
|
($idxlist ? "purgeSendqueue:-all-,-permError-,$idxlist " : "purgeSendqueue:-all-,-permError- ")
|
|
;
|
|
}
|
|
|
|
if($hash->{CREDENTIALS} && $model eq "Tasks") { # Model Aufgabenliste
|
|
$setlist .= "cleanCompleteTasks:noArg ";
|
|
}
|
|
|
|
my $params = {
|
|
hash => $hash,
|
|
name => $name,
|
|
opt => $opt,
|
|
prop => $prop,
|
|
prop1 => $prop1,
|
|
aref => \@a,
|
|
};
|
|
|
|
if($hset{$opt} && defined &{$hset{$opt}{fn}}) {
|
|
my $ret = q{};
|
|
|
|
if (!$hash->{CREDENTIALS} && $hset{$opt}{needcred}) {
|
|
return qq{Credentials of $name are not set. Make sure they are set with "set $name credentials <username> <password>"};
|
|
}
|
|
|
|
$ret = &{$hset{$opt}{fn}} ($params);
|
|
|
|
return $ret;
|
|
}
|
|
|
|
return "$setlist";
|
|
}
|
|
|
|
######################################################################################
|
|
# Setter credentials
|
|
######################################################################################
|
|
sub _setcredentials {
|
|
my $paref = shift;
|
|
my $hash = $paref->{hash};
|
|
my $name = $paref->{name};
|
|
my $opt = $paref->{opt};
|
|
my $prop = $paref->{prop};
|
|
my $prop1 = $paref->{prop1};
|
|
|
|
return qq{The command "$opt" needs an argument.} if (!$prop);
|
|
|
|
if($hash->{HELPER}{SID}) {
|
|
logout($hash, $data{SSCal}{$name}{calapi}, $splitstr); # Session alter User beenden falls vorhanden
|
|
}
|
|
|
|
my ($success) = setCredentials($hash, "credentials", $prop, $prop1, $splitstr);
|
|
|
|
if($success) {
|
|
my $params = {
|
|
name => $name,
|
|
opmode => "listcal",
|
|
api => "CAL",
|
|
method => "list",
|
|
params => "&is_todo=true&is_evt=true",
|
|
};
|
|
|
|
addSendqueue ($params);
|
|
getApiSites ($name);
|
|
return "credentials saved successfully";
|
|
}
|
|
else {
|
|
return "Error while saving credentials - see logfile for details";
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
######################################################################################
|
|
# Setter calUpdate
|
|
# Termine einer Cal_id (Liste) in Zeitgrenzen abrufen
|
|
######################################################################################
|
|
sub _setcalUpdate {
|
|
my $paref = shift;
|
|
my $hash = $paref->{hash};
|
|
my $name = $paref->{name};
|
|
my $opt = $paref->{opt};
|
|
my $aref = $paref->{aref};
|
|
|
|
if(!$hash->{HELPER}{CALFETCHED}) {
|
|
return qq{Obtain the Calendar list first with "get $name getCalendars" command.};
|
|
}
|
|
|
|
my $model = $hash->{MODEL};
|
|
my $cals = AttrVal($name,"usedCalendars", "");
|
|
|
|
my @a = @$aref;
|
|
shift @a; shift @a;
|
|
|
|
my $c = join(" ", @a);
|
|
$cals = $c ? $c : $cals;
|
|
|
|
if(!$cals) {
|
|
return qq{Please set attribute "usedCalendars" or specify the Calendar(s) you want read in "$opt" command.};
|
|
}
|
|
|
|
# Kalender aufsplitten und zu jedem die ID ermitteln
|
|
my @ca = split(",", $cals);
|
|
|
|
my ($oids,@cas);
|
|
|
|
my $caltype = $model eq "Diary" ? "Event" : "ToDo";
|
|
|
|
for my $cal (@ca) {
|
|
my $oid = $hash->{HELPER}{CALENDARS}{"$cal"}{id};
|
|
next if(!$oid);
|
|
|
|
if ($hash->{HELPER}{CALENDARS}{"$cal"}{type} ne $caltype) {
|
|
Log3($name, 3, qq{$name - The Calendar "$cal" is not of type "$caltype" and will be ignored.});
|
|
next;
|
|
}
|
|
|
|
$oids .= "," if($oids);
|
|
$oids .= '"'.$oid.'"';
|
|
push (@cas, $cal);
|
|
|
|
if(!$oid) {
|
|
Log3($name, 2, qq{$name - WARNING - The Calendar "$cal" seems to be unknown because its ID couldn't be found.});
|
|
}
|
|
}
|
|
|
|
if(!$oids) {
|
|
return qq{No Calendar of type "$caltype" was selected or its ID(s) couldn't be found.};
|
|
}
|
|
|
|
Log3($name, 5, "$name - Calendar selection for add queue: ".join(',', @cas));
|
|
|
|
if($model eq "Diary") { # Modell Terminkalender
|
|
my ($err,$tstart,$tend) = timeEdge ($name);
|
|
|
|
if($err) {
|
|
Log3($name, 2, "$name - ERROR in timestamp: $err");
|
|
|
|
my $errorcode = "910";
|
|
|
|
readingsBeginUpdate ($hash);
|
|
readingsBulkUpdateIfChanged ($hash, "Error", $err);
|
|
readingsBulkUpdateIfChanged ($hash, "Errorcode", $errorcode);
|
|
readingsBulkUpdate ($hash, "state", "Error");
|
|
readingsEndUpdate ($hash,1);
|
|
|
|
return "ERROR in timestamp: $err";
|
|
}
|
|
|
|
my $lr = AttrVal($name,"showRepeatEvent", "true");
|
|
|
|
my $params = {
|
|
name => $name,
|
|
opmode => "eventlist",
|
|
api => "EVENT",
|
|
method => "list",
|
|
params => "&cal_id_list=[$oids]&start=$tstart&end=$tend&list_repeat=$lr",
|
|
};
|
|
|
|
addSendqueue ($params);
|
|
getApiSites ($name);
|
|
}
|
|
else { # Modell Aufgabenliste
|
|
my $limit = ""; # Limit of matched tasks
|
|
my $offset = 0; # offset of mnatched tasks
|
|
my $filterdue = AttrVal($name,"filterDueTask", 3); # show tasks with and without due time
|
|
my $filtercomplete = AttrVal($name,"filterCompleteTask", 3); # show completed and not completed tasks
|
|
|
|
my $params = {
|
|
name => $name,
|
|
opmode => "todolist",
|
|
api => "TODO",
|
|
method => "list",
|
|
params => "&cal_id_list=[$oids]&limit=$limit&offset=$offset&filter_due=$filterdue&filter_complete=$filtercomplete",
|
|
};
|
|
|
|
addSendqueue ($params);
|
|
getApiSites ($name);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
######################################################################################
|
|
# Setter cleanCompleteTasks
|
|
# erledigte Aufgaben einer Cal_id (Liste) löschen
|
|
######################################################################################
|
|
sub _setcleanCompleteTasks {
|
|
my $paref = shift;
|
|
my $hash = $paref->{hash};
|
|
my $name = $paref->{name};
|
|
my $opt = $paref->{opt};
|
|
my $aref = $paref->{aref};
|
|
|
|
if(!$hash->{HELPER}{CALFETCHED}) {
|
|
return qq{Obtain the Calendar list first with "get $name getCalendars" command.};
|
|
}
|
|
|
|
my $cals = AttrVal($name,"usedCalendars", "");
|
|
|
|
my @a = @$aref;
|
|
shift @a; shift @a;
|
|
|
|
my $c = join(" ", @a);
|
|
$cals = $c ? $c : $cals;
|
|
|
|
if(!$cals) {
|
|
return qq{Please set attribute "usedCalendars" to specify the used calendars.};
|
|
}
|
|
|
|
# Kalender aufsplitten und zu jedem die ID ermitteln
|
|
my @ca = split(",", $cals);
|
|
my $oids;
|
|
|
|
for my $cal (@ca) {
|
|
my $oid = $hash->{HELPER}{CALENDARS}{"$cal"}{id};
|
|
next if(!$oid);
|
|
|
|
if ($hash->{HELPER}{CALENDARS}{"$cal"}{type} ne "ToDo") {
|
|
Log3($name, 3, qq{$name - The Calendar "$cal" is not of type "ToDo" and will be ignored.});
|
|
next;
|
|
}
|
|
|
|
$oids .= "," if($oids);
|
|
$oids .= '"'.$oid.'"';
|
|
|
|
if(!$oid) {
|
|
Log3($name, 2, qq{$name - WARNING - The Calendar "$cal" seems to be unknown because its ID couldn't be found.});
|
|
}
|
|
}
|
|
|
|
if(!$oids) {
|
|
return qq{No Calendar of type "ToDo" was selected or its ID(s) couldn't be found.};
|
|
}
|
|
|
|
Log3($name, 5, "$name - Calendar selection for add queue: $cals");
|
|
|
|
# <Name, operation mode, API (siehe $data{SSCal}{$name}{calapi}), auszuführende API-Methode, spezifische API-Parameter>
|
|
my $params = {
|
|
name => $name,
|
|
opmode => "cleanCompleteTasks",
|
|
api => "TODO",
|
|
method => "clean_complete",
|
|
params => "&cal_id_list=[$oids]",
|
|
};
|
|
|
|
addSendqueue ($params);
|
|
getApiSites ($name);
|
|
|
|
return;
|
|
}
|
|
|
|
######################################################################################
|
|
# Setter deleteEventId
|
|
######################################################################################
|
|
sub _setdeleteEventId {
|
|
my $paref = shift;
|
|
my $hash = $paref->{hash};
|
|
my $name = $paref->{name};
|
|
my $prop = $paref->{prop} // return "You must specify an event id (Reading EventId) what is to be deleted.";
|
|
|
|
my $eventid = $prop;
|
|
|
|
my $bnr; # Blocknummer ermitteln
|
|
my @allrds = keys%{$defs{$name}{READINGS}};
|
|
|
|
for my $key(@allrds) {
|
|
next if $key !~ /_EventId$/x;
|
|
if($defs{$name}{READINGS}{$key}{VAL} == $eventid) { # Blocknummer ermittelt
|
|
$bnr = (split("_", $key))[0];
|
|
}
|
|
}
|
|
|
|
if(!defined $bnr) {
|
|
return "The blocknumber of specified event id could not be identified. Make sure you have specified a valid event id.";
|
|
}
|
|
|
|
my $sum = ReadingsVal($name, $bnr."_01_Summary", ""); # die Summary zur Event Id ermitteln
|
|
|
|
my $calname = ReadingsVal($name, $bnr."_90_calName", ""); # Kalendername und dessen id und Typ ermitteln
|
|
my $calid = $hash->{HELPER}{CALENDARS}{"$calname"}{id};
|
|
my $caltype = $hash->{HELPER}{CALENDARS}{"$calname"}{type};
|
|
|
|
my $api = ($caltype eq "Event") ? "EVENT" : "TODO"; # Kalender-API in Abhängigkeit des Kalendertyps wählen
|
|
|
|
Log3($name, 3, qq{$name - The event "$sum" with id "$eventid" will be deleted in calendar "$calname".});
|
|
|
|
# <Name, operation mode, API (siehe $data{SSCal}{$name}{calapi}), auszuführende API-Methode, spezifische API-Parameter>
|
|
my $params = {
|
|
name => $name,
|
|
opmode => "deleteEventId",
|
|
api => $api,
|
|
method => "delete",
|
|
params => "delete","&evt_id=$eventid",
|
|
};
|
|
|
|
addSendqueue ($params);
|
|
getApiSites ($name);
|
|
|
|
return;
|
|
}
|
|
|
|
######################################################################################
|
|
# Setter restartSendqueue
|
|
######################################################################################
|
|
sub _setrestartSendqueue {
|
|
my $paref = shift;
|
|
my $name = $paref->{name};
|
|
|
|
my $ret = getApiSites($name);
|
|
|
|
if($ret) {
|
|
return $ret;
|
|
}
|
|
else {
|
|
return "The SendQueue has been restarted.";
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
######################################################################################
|
|
# Setter eraseReadings
|
|
######################################################################################
|
|
sub _seteraseReadings {
|
|
my $paref = shift;
|
|
my $name = $paref->{name};
|
|
|
|
delReadings($name, 0); # Readings löschen
|
|
|
|
return;
|
|
}
|
|
|
|
######################################################################################
|
|
# Setter logout
|
|
######################################################################################
|
|
sub _setlogout {
|
|
my $paref = shift;
|
|
my $hash = $paref->{hash};
|
|
my $name = $paref->{name};
|
|
|
|
logout($hash, $data{SSCal}{$name}{calapi}, $splitstr);
|
|
|
|
delete $data{SSCal}{$name}{calapi}{PARSET}; # erzwinge Abruf API beim nächsten Login
|
|
|
|
return;
|
|
}
|
|
|
|
######################################################################################
|
|
# Getter
|
|
######################################################################################
|
|
sub 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 $getlist;
|
|
|
|
if(!$hash->{CREDENTIALS}) {
|
|
return;
|
|
}
|
|
else {
|
|
$getlist = "Unknown argument $opt, choose one of ".
|
|
"apiInfo:noArg ".
|
|
"calAsHtml:noArg ".
|
|
"getCalendars:noArg ".
|
|
"storedCredentials:noArg ".
|
|
"versionNotes "
|
|
;
|
|
}
|
|
|
|
return if(IsDisabled($name));
|
|
|
|
my $params = {
|
|
hash => $hash,
|
|
name => $name,
|
|
opt => $opt,
|
|
arg => $arg,
|
|
};
|
|
|
|
if($hget{$opt} && defined &{$hget{$opt}{fn}}) {
|
|
my $ret = q{};
|
|
|
|
if (!$hash->{CREDENTIALS} && $hget{$opt}{needcred}) {
|
|
return qq{Credentials of $name are not set. Make sure they are set with "set $name credentials <username> <password>"};
|
|
}
|
|
|
|
$ret = &{$hget{$opt}{fn}} ($params);
|
|
|
|
return $ret;
|
|
}
|
|
|
|
return $getlist;
|
|
}
|
|
|
|
######################################################################################
|
|
# Getter storedCredentials
|
|
######################################################################################
|
|
sub _getstoredCredentials {
|
|
my $paref = shift;
|
|
my $hash = $paref->{hash};
|
|
|
|
my $out = showStoredCredentials ($hash, 1, $splitstr);
|
|
|
|
return $out;
|
|
}
|
|
|
|
######################################################################################
|
|
# Getter apiInfo
|
|
# Informationen der verwendeten API abrufen und anzeigen
|
|
######################################################################################
|
|
sub _getapiInfo {
|
|
my $paref = shift;
|
|
my $hash = $paref->{hash};
|
|
my $name = $paref->{name};
|
|
|
|
delClHash ($name);
|
|
getClHash ($hash,1); # übergebenen CL-Hash (FHEMWEB) in Helper eintragen
|
|
|
|
# Schema: <Name, operation mode, API (siehe $data{SSCal}{$name}{calapi}), auszuführende API-Methode, spezifische API-Parameter>
|
|
my $params = {
|
|
name => $name,
|
|
opmode => "apiInfo",
|
|
api => "",
|
|
method => "",
|
|
params => "",
|
|
};
|
|
|
|
addSendqueue ($params);
|
|
getApiSites ($name);
|
|
|
|
return;
|
|
}
|
|
|
|
######################################################################################
|
|
# Getter getCalendars
|
|
# Liste aller Kalender abrufen und anzeigen
|
|
######################################################################################
|
|
sub _getgetCalendars {
|
|
my $paref = shift;
|
|
my $hash = $paref->{hash};
|
|
my $name = $paref->{name};
|
|
|
|
delClHash ($name);
|
|
getClHash ($hash,1); # übergebenen CL-Hash (FHEMWEB) in Helper eintragen
|
|
|
|
# Schema: <Name, operation mode, API (siehe $data{SSCal}{$name}{calapi}), auszuführende API-Methode, spezifische API-Parameter>
|
|
my $params = {
|
|
name => $name,
|
|
opmode => "listcal",
|
|
api => "CAL",
|
|
method => "list",
|
|
params => "&is_todo=true&is_evt=true",
|
|
};
|
|
|
|
addSendqueue ($params);
|
|
getApiSites ($name);
|
|
|
|
return;
|
|
}
|
|
|
|
######################################################################################
|
|
# Getter calAsHtml
|
|
######################################################################################
|
|
sub _getcalAsHtml {
|
|
my $paref = shift;
|
|
my $name = $paref->{name};
|
|
|
|
my $out = calAsHtml($name);
|
|
|
|
return $out;
|
|
}
|
|
|
|
######################################################################################
|
|
# Getter versionNotes
|
|
######################################################################################
|
|
sub _getversionNotes {
|
|
my $paref = shift;
|
|
|
|
$paref->{hintextde} = \%vHintsExt_de;
|
|
$paref->{hintexten} = \%vHintsExt_en;
|
|
$paref->{notesext} = \%vNotesExtern;
|
|
|
|
my $ret = showModuleInfo ($paref);
|
|
|
|
return $ret;
|
|
}
|
|
|
|
######################################################################################
|
|
# Kalenderübersicht in Detailanzeige darstellen
|
|
######################################################################################
|
|
sub FWdetailFn {
|
|
my ($FW_wname, $d, $room, $pageHash) = @_;
|
|
my $hash = $defs{$d};
|
|
my $ret = "";
|
|
|
|
$hash->{".calhtml"} = FHEM::SSCal::calAsHtml($d,$FW_wname);
|
|
|
|
if($hash->{".calhtml"} ne "" && !$room && AttrVal($d,"tableInDetail",1)) { # Anzeige Übersicht in Detailansicht
|
|
$ret .= $hash->{".calhtml"};
|
|
return $ret;
|
|
}
|
|
|
|
if($hash->{".calhtml"} ne "" && $room && AttrVal($d,"tableInRoom",1)) { # Anzeige in Raumansicht zusätzlich zur Statuszeile
|
|
$ret = $hash->{".calhtml"};
|
|
return $ret;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
######################################################################################
|
|
# initiale Startroutinen nach Restart FHEM
|
|
######################################################################################
|
|
sub initOnBoot {
|
|
my ($name) = @_;
|
|
my $hash = $defs{$name};
|
|
my ($ret);
|
|
|
|
RemoveInternalTimer($name, "FHEM::SSCal::initOnBoot");
|
|
|
|
if ($init_done) {
|
|
CommandGet(undef, "$name getCalendars"); # Kalender Liste initial abrufen
|
|
}
|
|
else {
|
|
InternalTimer(gettimeofday()+3, "FHEM::SSCal::initOnBoot", $name, 0);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
#############################################################################################
|
|
# regelmäßiger Intervallabruf
|
|
#############################################################################################
|
|
sub periodicCall {
|
|
my $name = shift;
|
|
my $hash = $defs{$name};
|
|
|
|
RemoveInternalTimer($name,"FHEM::SSCal::periodicCall");
|
|
|
|
my $interval = AttrVal($name, "interval", 0);
|
|
|
|
my $new;
|
|
|
|
if(!$interval) {
|
|
$hash->{MODE} = "Manual";
|
|
readingsSingleUpdate($hash, "nextUpdate", "manual", 1);
|
|
return;
|
|
}
|
|
else {
|
|
$new = gettimeofday()+$interval;
|
|
readingsBeginUpdate ($hash);
|
|
readingsBulkUpdate ($hash, "nextUpdate", "Automatic - next polltime: ".FmtTime($new)); # Abrufmode initial auf "Manual" setzen
|
|
readingsEndUpdate ($hash,1);
|
|
|
|
$hash->{MODE} = "Automatic";
|
|
}
|
|
|
|
if($hash->{CREDENTIALS} && !IsDisabled($name)) {
|
|
CommandSet(undef, "$name calUpdate"); # Einträge aller gewählter Kalender oder Aufgabenlisten abrufen (in Queue stellen)
|
|
}
|
|
|
|
InternalTimer($new, "FHEM::SSCal::periodicCall", $name, 0);
|
|
|
|
return;
|
|
}
|
|
|
|
####################################################################################
|
|
# Einstiegsfunktion Queue Abarbeitung
|
|
####################################################################################
|
|
sub getApiSites {
|
|
my $name = shift;
|
|
my $hash = $defs{$name};
|
|
my $addr = $hash->{SERVERADDR};
|
|
my $port = $hash->{SERVERPORT};
|
|
my $prot = $hash->{PROTOCOL};
|
|
|
|
my ($url,$idxset,$ret);
|
|
|
|
$hash->{HELPER}{LOGINRETRIES} = 0;
|
|
|
|
my ($err,$tstart,$tend) = timeEdge($name);
|
|
$tstart = FmtDateTime($tstart);
|
|
$tend = FmtDateTime($tend);
|
|
|
|
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
|
|
for 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;
|
|
}
|
|
}
|
|
|
|
Log3($name, 4, "$name - ####################################################");
|
|
Log3($name, 4, "$name - ### start Calendar operation $hash->{OPMODE} ");
|
|
Log3($name, 4, "$name - ####################################################");
|
|
|
|
if(!$idxset) {
|
|
$ret = qq{Only entries with "forbidSend" are in Sendqueue. Escaping ...};
|
|
Log3($name, 4, "$name - $ret");
|
|
return $ret;
|
|
}
|
|
|
|
readingsBeginUpdate ($hash);
|
|
readingsBulkUpdate ($hash, "state", "running");
|
|
readingsEndUpdate ($hash,1);
|
|
|
|
Log3($name, 4, "$name - Time selection start: ".$tstart);
|
|
Log3($name, 4, "$name - Time selection end: ".$tend);
|
|
|
|
if ($hash->{OPMODE} eq "apiInfo") {
|
|
$data{SSCal}{$name}{calapi}{PARSET} = 0; # erzwinge Abruf API
|
|
}
|
|
|
|
if ($data{SSCal}{$name}{calapi}{PARSET}) { # API-Hashwerte sind bereits gesetzt -> Abruf überspringen
|
|
Log3($name, 4, "$name - API hash values already set - ignore get apisites");
|
|
return checkSID($name);
|
|
}
|
|
|
|
my $timeout = AttrVal($name,"timeout",20);
|
|
Log3($name, 5, "$name - HTTP-Call will be done with timeout: $timeout s");
|
|
|
|
# API initialisieren und abrufen
|
|
####################################
|
|
$data{SSCal}{$name}{calapi} = apistatic ("calendar"); # API Template im HELPER instanziieren
|
|
|
|
Log3 ($name, 4, "$name - API imported:\n".Dumper $data{SSCal}{$name}{calapi});
|
|
|
|
my @ak;
|
|
for my $key (keys %{$data{SSCal}{$name}{calapi}}) {
|
|
next if($key =~ /^PARSET$/x);
|
|
push @ak, $data{SSCal}{$name}{calapi}{$key}{NAME};
|
|
}
|
|
my $apis = join ",", @ak;
|
|
|
|
my $calapi = $data{SSCal}{$name}{calapi};
|
|
$url = "$prot://$addr:$port/webapi/$data{SSCal}{$name}{calapi}{INFO}{PATH}?".
|
|
"api=$data{SSCal}{$name}{calapi}{INFO}{NAME}".
|
|
"&method=Query".
|
|
"&version=$data{SSCal}{$name}{calapi}{INFO}{VER}".
|
|
"&query=$apis";
|
|
|
|
Log3($name, 4, "$name - Call-Out: $url");
|
|
|
|
my $param = {
|
|
url => $url,
|
|
timeout => $timeout,
|
|
hash => $hash,
|
|
method => "GET",
|
|
header => "Accept: application/json",
|
|
callback => \&FHEM::SSCal::getApiSites_parse
|
|
};
|
|
|
|
HttpUtils_NonblockingGet ($param);
|
|
|
|
return;
|
|
}
|
|
|
|
####################################################################################
|
|
# Auswertung Abruf apisites
|
|
####################################################################################
|
|
sub getApiSites_parse { ## no critic 'complexity'
|
|
my ($param, $err, $myjson) = @_;
|
|
my $hash = $param->{hash};
|
|
my $name = $hash->{NAME};
|
|
my $opmode = $hash->{OPMODE};
|
|
|
|
my ($error,$errorcode,$success);
|
|
|
|
if ($err ne "") { # wenn ein Fehler bei der HTTP Abfrage aufgetreten ist
|
|
Log3($name, 2, "$name - ERROR message: $err");
|
|
|
|
setReadingErrorState ($hash, $err);
|
|
checkSendRetry ($name, 1, $queueStartFn);
|
|
return;
|
|
}
|
|
elsif ($myjson ne "") {
|
|
($success) = evaljson($hash,$myjson);
|
|
|
|
if (!$success) {
|
|
Log3 ($name, 4, "$name - Data returned: ".$myjson);
|
|
checkSendRetry ($name, 1, $queueStartFn);
|
|
return;
|
|
}
|
|
|
|
my $jdata = decode_json($myjson);
|
|
|
|
Log3($name, 5, "$name - JSON returned: ". Dumper $jdata);
|
|
|
|
$success = $jdata->{'success'};
|
|
|
|
if ($success) {
|
|
my $completed = completeAPI ($jdata, $data{SSCal}{$name}{calapi}); # übergibt Referenz zum instanziierten API-Hash
|
|
|
|
if(!$completed) {
|
|
$errorcode = "9001";
|
|
$error = expErrors($hash,$errorcode); # Fehlertext zum Errorcode ermitteln
|
|
|
|
setReadingErrorState ($hash, $error, $errorcode);
|
|
Log3($name, 2, "$name - ERROR - $error");
|
|
|
|
checkSendRetry ($name, 1, $queueStartFn);
|
|
return;
|
|
}
|
|
|
|
# Downgrades für nicht kompatible API-Versionen. Hier nur nutzen wenn API zentral downgraded werden soll
|
|
Log3($name, 4, "$name - ------- Begin of adaption section -------");
|
|
|
|
my $adavs = "a01"; # adaptierte Version
|
|
|
|
if($adavs) {
|
|
for my $av (sort keys %{$hvada{$adavs}}) {
|
|
$data{SSCal}{$name}{calapi}{$av}{VER} = $hvada{$adavs}{$av};
|
|
$data{SSCal}{$name}{calapi}{$av}{MOD} = "yes";
|
|
Log3($name, 4, "$name - Version of $data{SSCal}{$name}{calapi}{$av}{NAME} adapted to: $data{SSCal}{$name}{calapi}{$av}{VER}");
|
|
}
|
|
}
|
|
|
|
Log3($name, 4, "$name - ------- End of adaption section -------");
|
|
|
|
setReadingErrorNone($hash, 1);
|
|
|
|
Log3 ($name, 4, "$name - API completed:\n".Dumper $data{SSCal}{$name}{calapi});
|
|
|
|
if ($opmode eq "apiInfo") { # API Infos in Popup anzeigen
|
|
showAPIinfo ($hash, $data{SSCal}{$name}{calapi}); # übergibt Referenz zum instanziierten API-Hash)
|
|
readingsSingleUpdate ($hash, "state", "done", 1);
|
|
checkSendRetry ($name, 0, $queueStartFn);
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
$errorcode = "806";
|
|
$error = expErrors($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");
|
|
|
|
checkSendRetry ($name, 1, $queueStartFn);
|
|
return;
|
|
}
|
|
}
|
|
|
|
return checkSID($name);
|
|
}
|
|
|
|
#############################################################################################
|
|
# Ausführung Operation
|
|
#############################################################################################
|
|
sub calOp {
|
|
my ($name) = @_;
|
|
my $hash = $defs{$name};
|
|
my $prot = $hash->{PROTOCOL};
|
|
my $addr = $hash->{SERVERADDR};
|
|
my $port = $hash->{SERVERPORT};
|
|
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};
|
|
my $calapi = $data{SSCal}{$name}{calapi};
|
|
|
|
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/".$calapi->{$api}{PATH}."?api=".$calapi->{$api}{NAME}."&version=".$calapi->{$api}{VER}."&method=$method".$params."&_sid=$sid";
|
|
|
|
if($opmode eq "deleteEventId" && $api eq "EVENT") { # Workaround !!! Methode delete funktioniert nicht mit SYNO.Cal.Event version > 1
|
|
$url = "$prot://$addr:$port/webapi/".$calapi->{$api}{PATH}."?api=".$calapi->{$api}{NAME}."&version=1&method=$method".$params."&_sid=$sid";
|
|
}
|
|
|
|
my $part = $url;
|
|
if(AttrVal($name, "showPassInLog", "0") == 1) {
|
|
Log3($name, 4, "$name - Call-Out: $url");
|
|
}
|
|
else {
|
|
$part =~ s/$sid/<secret>/x;
|
|
Log3($name, 4, "$name - Call-Out: $part");
|
|
}
|
|
|
|
$param = {
|
|
url => $url,
|
|
timeout => $timeout,
|
|
hash => $hash,
|
|
method => "GET",
|
|
header => "Accept: application/json",
|
|
callback => \&FHEM::SSCal::calOp_parse
|
|
};
|
|
|
|
HttpUtils_NonblockingGet ($param);
|
|
|
|
return;
|
|
}
|
|
|
|
#############################################################################################
|
|
# Callback from calOp
|
|
#############################################################################################
|
|
sub calOp_parse { ## no critic 'complexity'
|
|
my ($param, $err, $myjson) = @_;
|
|
my $hash = $param->{hash};
|
|
my $name = $hash->{NAME};
|
|
my $prot = $hash->{PROTOCOL};
|
|
my $addr = $hash->{SERVERADDR};
|
|
my $port = $hash->{SERVERPORT};
|
|
my $opmode = $hash->{OPMODE};
|
|
my $am = AttrVal($name, "asyncMode", 0);
|
|
my ($ts,$data,$success,$error,$errorcode,$cherror,$r);
|
|
|
|
if ($err ne "") { # wenn ein Fehler bei der HTTP Abfrage aufgetreten ist
|
|
Log3($name, 2, "$name - ERROR message: $err");
|
|
|
|
$errorcode = "none";
|
|
$errorcode = "800" if($err =~ /:\smalformed\sor\sunsupported\sURL$/xs);
|
|
|
|
readingsBeginUpdate ($hash);
|
|
readingsBulkUpdateIfChanged ($hash, "Error", $err);
|
|
readingsBulkUpdateIfChanged ($hash, "Errorcode", $errorcode);
|
|
readingsBulkUpdate ($hash, "state", "Error");
|
|
readingsEndUpdate ($hash,1);
|
|
|
|
checkSendRetry ($name, 1, $queueStartFn);
|
|
return;
|
|
}
|
|
elsif ($myjson ne "") { # wenn die Abfrage erfolgreich war ($data enthält die Ergebnisdaten des HTTP Aufrufes)
|
|
($success) = evaljson($hash,$myjson);
|
|
|
|
if (!$success) {
|
|
Log3 ($name, 4, "$name - Data returned: ".$myjson);
|
|
checkSendRetry ($name, 1, $queueStartFn);
|
|
return;
|
|
}
|
|
|
|
$data = decode_json($myjson);
|
|
|
|
Log3($name, 5, "$name - JSON returned: ". Dumper $data);
|
|
|
|
$success = $data->{'success'};
|
|
|
|
if ($success) {
|
|
|
|
if ($opmode eq "listcal") { ## no critic 'Cascading' # 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 = encode("UTF-8", $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);
|
|
for (@deva) {
|
|
push @newa, $_ if($_ !~ /usedCalendars:/x);
|
|
}
|
|
|
|
$cals =~ s/\s/#/gx 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});
|
|
|
|
checkSendRetry ($name, 0, $queueStartFn);
|
|
|
|
readingsBeginUpdate ($hash);
|
|
readingsBulkUpdateIfChanged ($hash, "Errorcode", "none");
|
|
readingsBulkUpdateIfChanged ($hash, "Error", "none");
|
|
readingsBulkUpdate ($hash, "state", "done");
|
|
readingsEndUpdate ($hash,1);
|
|
}
|
|
elsif ($opmode eq "eventlist") { # Events der ausgewählten Kalender aufbereiten
|
|
delete $data{SSCal}{$name}{eventlist}; # zentrales Event/ToDo Hash löschen
|
|
delete $data{SSCal}{$name}{vcalendar}; # zentrales VCALENDAR Hash löschen
|
|
$hash->{eventlist} = $data; # Data-Hashreferenz im Hash speichern
|
|
|
|
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("FHEM::SSCal::extractEventlist", $name, "FHEM::SSCal::createReadings", $timeout, "FHEM::SSCal::blockingTimeout", $hash);
|
|
$hash->{HELPER}{RUNNING_PID}{loglevel} = 5 if($hash->{HELPER}{RUNNING_PID}); # Forum #77057
|
|
}
|
|
else { # Extrahieren der Events synchron (blockierend)
|
|
Log3($name, 4, "$name - Event parse mode: synchronous");
|
|
extractEventlist ($name);
|
|
}
|
|
}
|
|
elsif ($opmode eq "todolist") { # ToDo's der ausgewählten Tasks-Kalender aufbereiten
|
|
delete $data{SSCal}{$name}{eventlist}; # zentrales Event/ToDo Hash löschen
|
|
$hash->{eventlist} = $data; # Data-Hashreferenz im Hash speichern
|
|
|
|
if ($am) { # Extrahieren der ToDos asynchron (nicht-blockierend)
|
|
Log3($name, 4, "$name - Task parse mode: asynchronous");
|
|
my $timeout = AttrVal($name, "timeout", 20)+180;
|
|
|
|
$hash->{HELPER}{RUNNING_PID} = BlockingCall("FHEM::SSCal::extractToDolist", $name, "FHEM::SSCal::createReadings", $timeout, "FHEM::SSCal::blockingTimeout", $hash);
|
|
$hash->{HELPER}{RUNNING_PID}{loglevel} = 5 if($hash->{HELPER}{RUNNING_PID}); # Forum #77057
|
|
}
|
|
else { # Extrahieren der ToDos synchron (blockierend)
|
|
Log3($name, 4, "$name - Task parse mode: synchronous");
|
|
extractToDolist ($name);
|
|
}
|
|
}
|
|
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");
|
|
|
|
checkSendRetry ($name, 0, $queueStartFn);
|
|
}
|
|
elsif ($opmode eq "deleteEventId") { # ein Kalendereintrag mit Event Id wurde gelöscht
|
|
readingsBeginUpdate ($hash);
|
|
readingsBulkUpdateIfChanged ($hash, "Errorcode", "none");
|
|
readingsBulkUpdateIfChanged ($hash, "Error", "none");
|
|
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};
|
|
|
|
checkSendRetry ($name, 0, $queueStartFn);
|
|
|
|
# Kalendereinträge neu einlesen nach dem löschen Event Id
|
|
CommandSet(undef, "$name calUpdate");
|
|
}
|
|
}
|
|
else {
|
|
# die API-Operation war fehlerhaft
|
|
# Errorcode aus JSON ermitteln
|
|
$errorcode = $data->{error}->{code};
|
|
$cherror = $data->{error}->{errors}; # vom cal gelieferter Fehler
|
|
$error = expErrors($hash,$errorcode); # Fehlertext zum Errorcode ermitteln
|
|
|
|
if ($error =~ /not found/) {
|
|
$error .= " New error: ".($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");
|
|
|
|
checkSendRetry ($name, 1, $queueStartFn);
|
|
}
|
|
|
|
undef $data;
|
|
undef $myjson;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
#############################################################################################
|
|
# Extrahiert empfangene Kalendertermine (Events)
|
|
#############################################################################################
|
|
sub extractEventlist { ## no critic 'complexity'
|
|
my $name = shift;
|
|
my $hash = $defs{$name};
|
|
my $data = delete $hash->{eventlist}; # zentrales Eventhash löschen !
|
|
my $am = AttrVal($name, "asyncMode", 0);
|
|
|
|
my ($tz,$bdate,$btime,$bts,$edate,$etime,$ets,$ci,$bi,$ei,$startEndDiff,$excl,$es,$em,$eh);
|
|
my ($nbdate,$nbtime,$nbts,$nedate,$netime,$nets);
|
|
my @row_array;
|
|
|
|
my (undef,$tstart,$tend) = timeEdge ($name); # Sollstart- und Sollendezeit der Kalenderereignisse ermitteln
|
|
my $datetimestart = FmtDateTime ($tstart);
|
|
my $datetimeend = FmtDateTime ($tend);
|
|
|
|
my $n = 0; # Zusatz f. lfd. Nr. zur Unterscheidung exakt zeitgleicher Events
|
|
for my $key (keys %{$data->{data}}) {
|
|
my $i = 0;
|
|
|
|
while ($data->{data}{$key}[$i]) {
|
|
my $ignore = 0;
|
|
my $done = 0;
|
|
my $next = 0;
|
|
($nbdate,$nedate) = ("","");
|
|
|
|
my $uid = $data->{data}{$key}[$i]{ical_uid}; # UID des Events
|
|
|
|
extractIcal ($name,$data->{data}{$key}[$i]); # VCALENDAR Extrakt in {HELPER}{VCALENDAR} importieren
|
|
|
|
my $isallday = $data->{data}{$key}[$i]{is_all_day};
|
|
($bi,$tz,$bdate,$btime,$bts,$excl) = explodeDateTime ($hash, $data->{data}{$key}[$i]{dtstart}, 0, 0, 0); # Beginn des Events
|
|
($ei,undef,$edate,$etime,$ets,undef) = explodeDateTime ($hash, $data->{data}{$key}[$i]{dtend}, $isallday, 0, 0); # Ende des Events
|
|
|
|
my ($byear, $bmonth, $bmday) = $bdate =~ /(\d{4})-(\d{2})-(\d{2})/x;
|
|
$nbtime = $btime;
|
|
|
|
my ($eyear, $emonth, $emday) = $edate =~ /(\d{4})-(\d{2})-(\d{2})/x;
|
|
$netime = $etime;
|
|
|
|
# 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$/x) {
|
|
($es, $em, $eh, $emday, $emonth, $eyear, undef, undef, undef) = localtime($ets-=3600);
|
|
$eyear = sprintf("%02d", $eyear+=1900);
|
|
$emonth = sprintf("%02d", $emonth+=1);
|
|
$emday = sprintf("%02d", $emday);
|
|
$netime = $eh.$em.$es;
|
|
$nbtime =~ s/://gx;
|
|
|
|
($bi,undef,$bdate,$btime,$bts,$excl) = explodeDateTime ($hash, $byear.$bmonth.$bmday."T".$nbtime, 0, 0, 0);
|
|
($ei,undef,$edate,$etime,$ets,undef) = explodeDateTime ($hash, $eyear.$emonth.$emday."T".$netime, 0, 0, 0);
|
|
}
|
|
|
|
$startEndDiff = $ets - $bts; # Differenz Event Ende / Start in Sekunden
|
|
|
|
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;
|
|
}
|
|
elsif ($excl) {
|
|
Log3($name, 4, "$name - Ignored by Ical compare -> $data->{data}{$key}[$i]{summary} start: $bdate $btime, end: $edate $etime");
|
|
|
|
$ignore = 1;
|
|
$done = 0;
|
|
}
|
|
else {
|
|
writeValuesToArray ({ name => $name,
|
|
eventno => $n,
|
|
calref => $data->{data}{$key}[$i],
|
|
timezone => $tz,
|
|
begindate => $bdate,
|
|
begintime => $btime,
|
|
begints => $bts,
|
|
enddate => $edate,
|
|
endtime => $etime,
|
|
endts => $ets,
|
|
sumarrayref => \@row_array,
|
|
uid => $uid
|
|
});
|
|
$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;
|
|
|
|
for my $par (@para) {
|
|
my ($p1,$p2) = split "=", $par;
|
|
|
|
if ($p1 eq "FREQ") {
|
|
$freq = $p2;
|
|
}
|
|
if ($p1 eq "COUNT") { # Event endet automatisch nach x Wiederholungen
|
|
$count = $p2;
|
|
}
|
|
if ($p1 eq "INTERVAL") { # Wiederholungsintervall
|
|
$interval = $p2;
|
|
}
|
|
if ($p1 eq "UNTIL") { # festes Intervallende angegeben
|
|
$until = $p2;
|
|
$until =~ s/[-:]//gx;
|
|
$uets = (explodeDateTime ($hash, $until, 0, 0, 0))[4];
|
|
}
|
|
if ($p1 eq "BYMONTHDAY") { # Wiederholungseigenschaft -> Tag des Monats z.B. 13 (Tag 13)
|
|
$bymonthday = $p2;
|
|
}
|
|
if ($p1 eq "BYDAY") { # Wiederholungseigenschaft -> Wochentag z.B. 2WE,-1SU,4FR (kann auch Liste bei WEEKLY sein)
|
|
$byday = $p2;
|
|
}
|
|
}
|
|
|
|
if (defined $uets && $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;
|
|
}
|
|
|
|
$count = $count // 9999999; # $count "unendlich" wenn kein COUNT angegeben
|
|
$interval = $interval // 1;
|
|
$bymonthday = $bymonthday // "";
|
|
$byday = $byday // "";
|
|
$until = $until // "";
|
|
|
|
Log3($name, 4, "$name - Recurring params - FREQ: $freq, COUNT: $count, INTERVAL: $interval, BYMONTHDAY: $bymonthday, BYDAY: $byday, UNTIL: $until");
|
|
|
|
$count--; # Korrektur Anzahl Wiederholungen, COUNT ist Gesamtzahl der Ausführungen !
|
|
|
|
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/://gx;
|
|
$netime =~ s/://gx;
|
|
|
|
my $dtstart = $byear.$bmonth.$bmday."T".$nbtime;
|
|
($bi,undef,$nbdate,$nbtime,$nbts,$excl) = explodeDateTime ($hash, $byear.$bmonth.$bmday."T".$nbtime, 0, $uid, $dtstart); # Beginn des Wiederholungsevents
|
|
($ei,undef,$nedate,$netime,$nets,undef) = explodeDateTime ($hash, $eyear.$emonth.$emday."T".$netime, 0, $uid, $dtstart); # Ende des Wiederholungsevents
|
|
|
|
Log3($name, 5, "$name - YEARLY event - Begin: $nbdate $nbtime, End: $nedate $netime");
|
|
|
|
($ignore, $done, $n, $next) = evalTimeAndWrite ({ recurring => 'YEARLY',
|
|
name => $name,
|
|
excl => $excl,
|
|
eventno => $n,
|
|
calref => $data->{data}{$key}[$i],
|
|
timezone => $tz,
|
|
begindate => $bdate,
|
|
newbdate => $nbdate,
|
|
begintime => $btime,
|
|
newbtime => $nbtime,
|
|
begints => $bts,
|
|
enddate => $edate,
|
|
newedate => $nedate,
|
|
endtime => $etime,
|
|
newetime => $netime,
|
|
endts => $ets,
|
|
newendts => $nets,
|
|
sumarrayref => \@row_array,
|
|
untilts => $uets,
|
|
newbegints => $nbts,
|
|
tstart => $tstart,
|
|
tend => $tend,
|
|
until => $until,
|
|
uid => $uid
|
|
});
|
|
|
|
next if($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 if($ci>=0);
|
|
$byear += int( $bmonth/13);
|
|
$bmonth %= 12 if($bmonth>12);
|
|
$bmonth = sprintf("%02d", $bmonth);
|
|
|
|
$emonth += $interval if($ci>=0);
|
|
$eyear += int( $emonth/13);
|
|
$emonth %= 12 if($emonth>12);
|
|
$emonth = sprintf("%02d", $emonth);
|
|
|
|
$nbtime =~ s/://gx;
|
|
$netime =~ s/://gx;
|
|
|
|
my $dtstart = $byear.$bmonth.$bmday."T".$nbtime;
|
|
($bi,undef,$nbdate,$nbtime,$nbts,$excl) = explodeDateTime ($hash, $byear.$bmonth.$bmday."T".$nbtime, 0, $uid, $dtstart); # Beginn des Wiederholungsevents
|
|
($ei,undef,$nedate,$netime,$nets,undef) = explodeDateTime ($hash, $eyear.$emonth.$emday."T".$netime, 0, $uid, $dtstart); # Ende des Wiederholungsevents
|
|
|
|
Log3($name, 5, "$name - MONTHLY event - Begin: $nbdate $nbtime, End: $nedate $netime");
|
|
|
|
($ignore, $done, $n, $next) = evalTimeAndWrite ({ recurring => 'MONTHLY',
|
|
name => $name,
|
|
excl => $excl,
|
|
eventno => $n,
|
|
calref => $data->{data}{$key}[$i],
|
|
timezone => $tz,
|
|
begindate => $bdate,
|
|
newbdate => $nbdate,
|
|
begintime => $btime,
|
|
newbtime => $nbtime,
|
|
begints => $bts,
|
|
enddate => $edate,
|
|
newedate => $nedate,
|
|
endtime => $etime,
|
|
newetime => $netime,
|
|
endts => $ets,
|
|
newendts => $nets,
|
|
sumarrayref => \@row_array,
|
|
untilts => $uets,
|
|
newbegints => $nbts,
|
|
tstart => $tstart,
|
|
tend => $tend,
|
|
until => $until,
|
|
uid => $uid
|
|
});
|
|
|
|
next if($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,$numOfRatedDay,$rDaysToAddOrSub,$rNewTime);
|
|
my @ByDays = split ",", $byday; # Array der Wiederholungstage
|
|
|
|
for my $rByDay (@ByDays) {
|
|
my $rByDayLength = length($rByDay); # die Länge des Strings
|
|
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
|
|
|
|
if ($rByDayLength > 2) {
|
|
$rDayStr = substr($rByDay, -2);
|
|
$rDayInterval = int(substr($rByDay, 0, $rByDayLength - 2));
|
|
}
|
|
else {
|
|
$rDayStr = $rByDay;
|
|
$rDayInterval = 1;
|
|
}
|
|
|
|
my $numOfAppointmentDay = _weekdayNumber ($rDayStr); # 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 if($ci>=0);
|
|
$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, $numOfRatedDay, undef, undef) = localtime($firstOfNextMonth); # den 1. des Monats sowie die dazu gehörige Nr. des Wochentages
|
|
|
|
if ($numOfRatedDay <= $numOfAppointmentDay) { # Nr Wochentag des 1. des Monats <= als Wiederholungstag
|
|
$rDaysToAddOrSub = $numOfAppointmentDay - $numOfRatedDay;
|
|
}
|
|
else {
|
|
$rDaysToAddOrSub = 7 - $numOfRatedDay + $numOfAppointmentDay;
|
|
}
|
|
$rDaysToAddOrSub += (7 * ($rDayInterval - 1)); # addiere Tagesintervall, z.B. 4th Freitag ...
|
|
|
|
$rNewTime = plusNSeconds($firstOfNextMonth, 86400*$rDaysToAddOrSub, 1);
|
|
($nbss,$nbmm,$nbhh,$bmday,$bmonth,$byear,$ness,$nemm,$nehh,$emday,$emonth,$eyear) = DTfromStartandDiff ($rNewTime,$startEndDiff);
|
|
}
|
|
else {
|
|
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;
|
|
|
|
my $dtstart = $byear.$bmonth.$bmday."T".$nbtime;
|
|
($bi,undef,$nbdate,$nbtime,$nbts,$excl) = explodeDateTime ($hash, $byear.$bmonth.$bmday."T".$nbtime, 0, $uid, $dtstart); # Beginn des Wiederholungsevents
|
|
($ei,undef,$nedate,$netime,$nets,undef) = explodeDateTime ($hash, $eyear.$emonth.$emday."T".$netime, 0, $uid, $dtstart); # Ende des Wiederholungsevents
|
|
|
|
Log3($name, 5, "$name - MONTHLY event - Begin: $nbdate $nbtime, End: $nedate $netime");
|
|
|
|
($ignore, $done, $n, $next) = evalTimeAndWrite ({ recurring => 'MONTHLY',
|
|
name => $name,
|
|
excl => $excl,
|
|
eventno => $n,
|
|
calref => $data->{data}{$key}[$i],
|
|
timezone => $tz,
|
|
begindate => $bdate,
|
|
newbdate => $nbdate,
|
|
begintime => $btime,
|
|
newbtime => $nbtime,
|
|
begints => $bts,
|
|
enddate => $edate,
|
|
newedate => $nedate,
|
|
endtime => $etime,
|
|
newetime => $netime,
|
|
endts => $ets,
|
|
newendts => $nets,
|
|
sumarrayref => \@row_array,
|
|
untilts => $uets,
|
|
newbegints => $nbts,
|
|
tstart => $tstart,
|
|
tend => $tend,
|
|
until => $until,
|
|
uid => $uid
|
|
});
|
|
|
|
next if($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,$rNewTime);
|
|
my ($numOfRatedDay,$rDaysToAddOrSub);
|
|
my @ByDays = split ",", $byday; # Array der Wiederholungstage
|
|
|
|
my $btsstart = $bts;
|
|
$ci = -1;
|
|
|
|
while ($ci<$count) {
|
|
$rNewTime = $btsstart;
|
|
|
|
for my $rByDay (@ByDays) {
|
|
$ci++;
|
|
|
|
my $numOfAppointmentDay = _weekdayNumber ($rByDay); # liefert Nr des Wochentages: SU = 0 ... SA = 6
|
|
|
|
($nbss, $nbmm, $nbhh, $bmday, $bmonth, $byear, $numOfRatedDay, undef, undef) = localtime($rNewTime);
|
|
|
|
($nbhh,$nbmm,$nbss) = split ":", $nbtime;
|
|
|
|
if ($numOfRatedDay <= $numOfAppointmentDay) { # Nr nächster Wochentag <= Planwochentag
|
|
$rDaysToAddOrSub = $numOfAppointmentDay - $numOfRatedDay;
|
|
}
|
|
else {
|
|
$rDaysToAddOrSub = - $numOfRatedDay + $numOfAppointmentDay + (7 * ($interval-1));
|
|
}
|
|
|
|
$rNewTime = plusNSeconds($rNewTime, 86400 * $rDaysToAddOrSub, 1);
|
|
($nbss,$nbmm,$nbhh,$bmday,$bmonth,$byear,$ness,$nemm,$nehh,$emday,$emonth,$eyear) = DTfromStartandDiff ($rNewTime,$startEndDiff);
|
|
|
|
$nbtime = $nbhh.$nbmm.$nbss;
|
|
$netime = $nehh.$nemm.$ness;
|
|
|
|
my $dtstart = $byear.$bmonth.$bmday."T".$nbtime;
|
|
($bi,undef,$nbdate,$nbtime,$nbts,$excl) = explodeDateTime ($hash, $byear.$bmonth.$bmday."T".$nbtime, 0, $uid, $dtstart); # Beginn des Wiederholungsevents
|
|
($ei,undef,$nedate,$netime,$nets,undef) = explodeDateTime ($hash, $eyear.$emonth.$emday."T".$netime, 0, $uid, $dtstart); # Ende des Wiederholungsevents
|
|
|
|
Log3($name, 5, "$name - WEEKLY event - Begin: $nbdate $nbtime, End: $nedate $netime");
|
|
|
|
($ignore, $done, $n, $next) = evalTimeAndWrite ({ recurring => 'WEEKLY',
|
|
name => $name,
|
|
excl => $excl,
|
|
eventno => $n,
|
|
calref => $data->{data}{$key}[$i],
|
|
timezone => $tz,
|
|
begindate => $bdate,
|
|
newbdate => $nbdate,
|
|
begintime => $btime,
|
|
newbtime => $nbtime,
|
|
begints => $bts,
|
|
enddate => $edate,
|
|
newedate => $nedate,
|
|
endtime => $etime,
|
|
newetime => $netime,
|
|
endts => $ets,
|
|
newendts => $nets,
|
|
sumarrayref => \@row_array,
|
|
untilts => $uets,
|
|
newbegints => $nbts,
|
|
tstart => $tstart,
|
|
tend => $tend,
|
|
until => $until,
|
|
uid => $uid
|
|
});
|
|
|
|
next if($next);
|
|
last if((defined $uets && ($uets < $nbts)) || $nbts > $tend);
|
|
}
|
|
last if((defined $uets && ($uets < $nbts)) || $nbts > $tend);
|
|
$btsstart += (7 * 86400 * $interval); # addiere Tagesintervall, z.B. 4th Freitag ...
|
|
}
|
|
}
|
|
else {
|
|
my ($nbhh,$nbmm,$nbss,$nehh,$nemm,$ness);
|
|
my $rNewTime = $bts;
|
|
|
|
for ($ci=-1; $ci<($count*$interval); $ci+=$interval) {
|
|
$rNewTime += $interval*604800 if($ci>=0); # Wochenintervall addieren
|
|
|
|
($nbss,$nbmm,$nbhh,$bmday,$bmonth,$byear,$ness,$nemm,$nehh,$emday,$emonth,$eyear) = DTfromStartandDiff ($rNewTime,$startEndDiff);
|
|
$nbtime = $nbhh.$nbmm.$nbss;
|
|
$netime = $nehh.$nemm.$ness;
|
|
|
|
my $dtstart = $byear.$bmonth.$bmday."T".$nbtime;
|
|
($bi,undef,$nbdate,$nbtime,$nbts,$excl) = explodeDateTime ($hash, $byear.$bmonth.$bmday."T".$nbtime, 0, $uid, $dtstart); # Beginn des Wiederholungsevents
|
|
($ei,undef,$nedate,$netime,$nets,undef) = explodeDateTime ($hash, $eyear.$emonth.$emday."T".$netime, 0, $uid, $dtstart); # Ende des Wiederholungsevents
|
|
|
|
Log3($name, 5, "$name - WEEKLY event - Begin: $nbdate $nbtime, End: $nedate $netime");
|
|
|
|
($ignore, $done, $n, $next) = evalTimeAndWrite ({ recurring => 'WEEKLY',
|
|
name => $name,
|
|
excl => $excl,
|
|
eventno => $n,
|
|
calref => $data->{data}{$key}[$i],
|
|
timezone => $tz,
|
|
begindate => $bdate,
|
|
newbdate => $nbdate,
|
|
begintime => $btime,
|
|
newbtime => $nbtime,
|
|
begints => $bts,
|
|
enddate => $edate,
|
|
newedate => $nedate,
|
|
endtime => $etime,
|
|
newetime => $netime,
|
|
endts => $ets,
|
|
newendts => $nets,
|
|
sumarrayref => \@row_array,
|
|
untilts => $uets,
|
|
newbegints => $nbts,
|
|
tstart => $tstart,
|
|
tend => $tend,
|
|
until => $until,
|
|
uid => $uid
|
|
});
|
|
|
|
next if($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*$interval if($ci>=0);
|
|
|
|
($nbss,$nbmm,$nbhh,$bmday,$bmonth,$byear,$ness,$nemm,$nehh,$emday,$emonth,$eyear) = DTfromStartandDiff ($bts,$startEndDiff);
|
|
|
|
$nbtime = $nbhh.$nbmm.$nbss;
|
|
$netime = $nehh.$nemm.$ness;
|
|
|
|
my $dtstart = $byear.$bmonth.$bmday."T".$nbtime;
|
|
($bi,undef,$nbdate,$nbtime,$nbts,$excl) = explodeDateTime ($hash, $byear.$bmonth.$bmday."T".$nbtime, 0, $uid, $dtstart); # Beginn des Wiederholungsevents
|
|
($ei,undef,$nedate,$netime,$nets,undef) = explodeDateTime ($hash, $eyear.$emonth.$emday."T".$netime, 0, $uid, $dtstart); # Ende des Wiederholungsevents
|
|
|
|
Log3($name, 5, "$name - DAILY event - Begin: $nbdate $nbtime, End: $nedate $netime");
|
|
|
|
($ignore, $done, $n, $next) = evalTimeAndWrite ({ recurring => 'DAILY',
|
|
name => $name,
|
|
excl => $excl,
|
|
eventno => $n,
|
|
calref => $data->{data}{$key}[$i],
|
|
timezone => $tz,
|
|
begindate => $bdate,
|
|
newbdate => $nbdate,
|
|
begintime => $btime,
|
|
newbtime => $nbtime,
|
|
begints => $bts,
|
|
enddate => $edate,
|
|
newedate => $nedate,
|
|
endtime => $etime,
|
|
newetime => $netime,
|
|
endts => $ets,
|
|
newendts => $nets,
|
|
sumarrayref => \@row_array,
|
|
untilts => $uets,
|
|
newbegints => $nbts,
|
|
tstart => $tstart,
|
|
tend => $tend,
|
|
until => $until,
|
|
uid => $uid
|
|
});
|
|
|
|
next if($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;
|
|
|
|
writeValuesToArray ({ name => $name,
|
|
eventno => $n,
|
|
calref => $data->{data}{$key}[$i],
|
|
timezone => $tz,
|
|
begindate => $bdate,
|
|
begintime => $btime,
|
|
begints => $bts,
|
|
enddate => $edate,
|
|
endtime => $etime,
|
|
endts => $ets,
|
|
sumarrayref => \@row_array,
|
|
uid => $uid
|
|
});
|
|
}
|
|
|
|
$i++;
|
|
$n++;
|
|
}
|
|
$n++;
|
|
}
|
|
|
|
# encoding result
|
|
my $rowlist = join('_ESC_', @row_array);
|
|
$rowlist = encode_base64($rowlist,"");
|
|
|
|
return "$name|$rowlist" if($am); # asynchroner Mode mit BlockingCall
|
|
|
|
return createReadings ("$name|$rowlist"); # synchoner Mode
|
|
}
|
|
|
|
#############################################################################################
|
|
# liefert Nr eines gegebenen Wochentages, z.B. SU = 0 ... SA = 6
|
|
#############################################################################################
|
|
sub _weekdayNumber {
|
|
my $dayString = shift;
|
|
|
|
my @weekdays = qw(SU MO TU WE TH FR SA);
|
|
my ($weekdayNum) = grep {$weekdays[$_] eq $dayString} 0..$#weekdays;
|
|
|
|
return $weekdayNum;
|
|
}
|
|
|
|
#############################################################################################
|
|
# Extrahiert empfangene Tasks aus ToDo-Kalender (Aufgabenliste)
|
|
#############################################################################################
|
|
sub extractToDolist { ## no critic 'complexity'
|
|
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,$bi,$ei,$startEndDiff,$excl);
|
|
my ($nbdate,$nbtime,$nbts,$nedate,$netime,$nets);
|
|
my @row_array;
|
|
|
|
my (undef,$tstart,$tend) = timeEdge($name); # Sollstart- und Sollendezeit der Kalenderereignisse ermitteln
|
|
my $datetimestart = FmtDateTime($tstart);
|
|
my $datetimeend = FmtDateTime($tend);
|
|
|
|
my $n = 0;
|
|
for my $key (keys %{$data->{data}}) {
|
|
my $i = 0;
|
|
|
|
while ($data->{data}{$key}[$i]) {
|
|
my $ignore = 0;
|
|
my $done = 0;
|
|
($nbdate,$nedate) = ("","");
|
|
|
|
my $uid = $data->{data}{$key}[$i]{ical_uid}; # UID des Events
|
|
|
|
($bi,$tz,$bdate,$btime,$bts,$excl) = explodeDateTime ($hash, $data->{data}{$key}[$i]{due}, 0, 0, 0); # Fälligkeit des Tasks (falls gesetzt)
|
|
($ei,undef,$edate,$etime,$ets,undef) = explodeDateTime ($hash, $data->{data}{$key}[$i]{due}, 0, 0, 0); # Ende = Fälligkeit des Tasks (falls gesetzt)
|
|
|
|
if ($bdate && $edate) { # nicht jede Aufgabe hat Date / Time gesetzt
|
|
my ($byear, $bmonth, $bmday) = $bdate =~ /(\d{4})-(\d{2})-(\d{2})/x;
|
|
$nbtime = $btime;
|
|
|
|
my ($eyear, $emonth, $emday) = $edate =~ /(\d{4})-(\d{2})-(\d{2})/x;
|
|
$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 {
|
|
writeValuesToArray ({ name => $name,
|
|
eventno => $n,
|
|
calref => $data->{data}{$key}[$i],
|
|
timezone => $tz,
|
|
begindate => $bdate,
|
|
begintime => $btime,
|
|
begints => $bts,
|
|
enddate => $edate,
|
|
endtime => $etime,
|
|
endts => $ets,
|
|
sumarrayref => \@row_array,
|
|
uid => $uid
|
|
});
|
|
$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;
|
|
|
|
writeValuesToArray ({ name => $name,
|
|
eventno => $n,
|
|
calref => $data->{data}{$key}[$i],
|
|
timezone => $tz,
|
|
begindate => $bdate,
|
|
begintime => $btime,
|
|
begints => $bts,
|
|
enddate => $edate,
|
|
endtime => $etime,
|
|
endts => $ets,
|
|
sumarrayref => \@row_array,
|
|
uid => $uid
|
|
});
|
|
}
|
|
$i++;
|
|
$n++;
|
|
}
|
|
$n++;
|
|
}
|
|
|
|
# encoding result
|
|
my $rowlist = join('_ESC_', @row_array);
|
|
$rowlist = encode_base64($rowlist,"");
|
|
|
|
return "$name|$rowlist" if($am); # asynchroner Mode mit BlockingCall
|
|
|
|
return createReadings ("$name|$rowlist"); # synchoner Mode
|
|
}
|
|
|
|
#############################################################################################
|
|
#
|
|
# bewertet Zeitgrenzen und excludierende Parameter - ruft Schreiben der Daten in Ergebnis
|
|
# Array auf wenn Prüfung positiv
|
|
#
|
|
#############################################################################################
|
|
sub evalTimeAndWrite {
|
|
my ($argref) = @_;
|
|
my $name = $argref->{name};
|
|
my $n = $argref->{eventno};
|
|
my $calref = $argref->{calref};
|
|
my $excl = $argref->{excl};
|
|
my $tz = $argref->{timezone};
|
|
my $bdate = $argref->{begindate};
|
|
my $nbdate = $argref->{newbdate};
|
|
my $btime = $argref->{begintime};
|
|
my $nbtime = $argref->{newbtime};
|
|
my $bts = $argref->{begints};
|
|
my $edate = $argref->{enddate};
|
|
my $nedate = $argref->{newedate};
|
|
my $etime = $argref->{endtime};
|
|
my $netime = $argref->{newetime};
|
|
my $ets = $argref->{endts};
|
|
my $uets = $argref->{untilts};
|
|
my $nbts = $argref->{newbegints};
|
|
my $tstart = $argref->{tstart};
|
|
my $tend = $argref->{tend};
|
|
my $nets = $argref->{newendts};
|
|
my $aref = $argref->{sumarrayref};
|
|
my $uid = $argref->{uid};
|
|
my $recurring = $argref->{recurring};
|
|
my $until = $argref->{until};
|
|
|
|
my $ignore = 0;
|
|
my $done = 0;
|
|
my $next = 0;
|
|
|
|
if (defined $uets && ($uets < $nbts)) { # Event Ende (UNTIL) kleiner aktueller Select Start
|
|
Log3($name, 4, "$name - Ignore ".$recurring." event - UNTIL out of time LIMIT (".$calref->{summary}." , start: $nbdate $nbtime, end: $nedate $netime, until: $until)");
|
|
$ignore = 1;
|
|
$done = 0;
|
|
}
|
|
elsif ($nets < $tstart || $nbts > $tend) { # Event Ende kleiner Select Start oder Beginn Event größer als Select Ende
|
|
Log3($name, 4, "$name - Ignore ".$recurring." event - out of selected time LIMITS (".$calref->{summary}." , start: $nbdate $nbtime, end: $nedate $netime)");
|
|
$ignore = 1;
|
|
$done = 0;
|
|
}
|
|
elsif ($nbts < $bts) {
|
|
Log3($name, 4, "$name - Ignore ".$recurring." event - calculated BEGIN is before DTSTART (".$calref->{summary}." , start: $nbdate $nbtime, end: $nedate $netime)");
|
|
$ignore = 1;
|
|
$done = 0;
|
|
}
|
|
elsif ($excl) {
|
|
Log3($name, 4, "$name - ".$recurring." recurring event - is DELETED (".$calref->{summary}." , start: $nbdate $nbtime, end: $nedate $netime)");
|
|
$ignore = 1;
|
|
$done = 0;
|
|
}
|
|
else {
|
|
$bdate = $nbdate // $bdate;
|
|
$btime = $nbtime // $btime;
|
|
$bts = $nbts // $bts;
|
|
|
|
$edate = $nedate // $edate;
|
|
$etime = $netime // $etime;
|
|
$ets = $nets // $ets;
|
|
|
|
writeValuesToArray ({ name => $name,
|
|
eventno => $n,
|
|
calref => $calref,
|
|
timezone => $tz,
|
|
begindate => $bdate,
|
|
begintime => $btime,
|
|
begints => $bts,
|
|
enddate => $edate,
|
|
endtime => $etime,
|
|
endts => $ets,
|
|
sumarrayref => $aref,
|
|
uid => $uid
|
|
});
|
|
$ignore = 0;
|
|
$done = 1;
|
|
$next = 1;
|
|
$n++;
|
|
}
|
|
|
|
return ($ignore, $done, $n, $next);
|
|
}
|
|
|
|
#############################################################################################
|
|
# schreibe Key/Value Pairs in zentrales Valuearray zur Readingerstellung
|
|
# $n = Zusatz f. lfd. Nr. zur Unterscheidung exakt
|
|
# zeitgleicher Events
|
|
# $vh = Referenz zum Kalenderdatenhash
|
|
# $aref = Rferenz zum Ergebnisarray
|
|
# $uid = UID des Ereignisses als Schlüssel im VCALENDER Hash
|
|
# (Berechnung der Vorwarnzeitenzeiten)
|
|
#
|
|
# Ergebisarray Aufbau:
|
|
# 0 1 2
|
|
# (Index aus BeginTimestamp + lfNr) , (Blockindex_Reading) , (Wert)
|
|
#
|
|
#############################################################################################
|
|
sub writeValuesToArray { ## no critic 'complexity'
|
|
my ($argref) = @_;
|
|
my $name = $argref->{name};
|
|
my $n = $argref->{eventno};
|
|
my $vh = $argref->{calref};
|
|
my $tz = $argref->{timezone};
|
|
my $bdate = $argref->{begindate};
|
|
my $btime = $argref->{begintime};
|
|
my $bts = $argref->{begints};
|
|
my $edate = $argref->{enddate};
|
|
my $etime = $argref->{endtime};
|
|
my $ets = $argref->{endts};
|
|
my $aref = $argref->{sumarrayref};
|
|
my $uid = $argref->{uid};
|
|
|
|
my $hash = $defs{$name};
|
|
my $lang = AttrVal("global", "language", "EN");
|
|
my $ts = time(); # Istzeit Timestamp
|
|
my $om = $hash->{OPMODE}; # aktuelle Operation Mode
|
|
my $status = "initialized";
|
|
my ($val,$uts,$td,$dleft,$bWday,$chts);
|
|
|
|
my ($upcoming,$alarmed,$started,$ended) = (0,0,0,0);
|
|
|
|
$upcoming = isUpcoming ($ts,0,$bts); # initiales upcoming
|
|
$started = isStarted ($ts,$bts,$ets);
|
|
$ended = isEnded ($ts,$ets);
|
|
|
|
if($bdate && $btime) {
|
|
push(@$aref, $bts+$n." 05_Begin " .$bdate." ".$btime."\n");
|
|
my ($ny,$nm,$nd,undef) = split(/[\s-]/x, TimeNow()); # Datum Jetzt
|
|
my ($by,$bm,$bd) = split("-", $bdate); # Beginn Datum
|
|
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);
|
|
}
|
|
|
|
my @days;
|
|
(undef, undef, undef, undef, undef, undef, $bWday, undef, undef) = localtime($btimes);
|
|
if($lang eq "DE") {
|
|
@days = qw(Sonntag Montag Dienstag Mittwoch Donnerstag Freitag Samstag);
|
|
}
|
|
else {
|
|
@days = qw(Sunday Monday Tuesday Wednesday Thursday Friday Saturday);
|
|
}
|
|
$bWday = $days[$bWday];
|
|
}
|
|
|
|
push(@$aref, $bts+$n." 10_End " .$edate." ".$etime."\n") if($edate && $etime);
|
|
push(@$aref, $bts+$n." 15_Timezone " .$tz."\n") if($tz);
|
|
push(@$aref, $bts+$n." 20_daysLeft " .$dleft."\n") if(defined $dleft);
|
|
push(@$aref, $bts+$n." 25_daysLeftLong " ."in ".$dleft." Tagen\n") if(defined $dleft);
|
|
push(@$aref, $bts+$n." 30_Weekday " .$bWday."\n") if(defined $bWday);
|
|
|
|
# Vorwarnzeiten für veränderte Serientermine korrigieren/anpassen
|
|
my $origdtstart = strftime "%Y%m%dT%H%M%S", localtime($bts);
|
|
my $isRecurrence = 0;
|
|
my $isAlldaychanded; # 0 -> Ganztagsevent wurde in Serienelement geändert in kein Ganztagsevent
|
|
|
|
for (keys %{$data{SSCal}{$name}{vcalendar}{"$uid"}{VALM}{RECURRENCEID}}) { # $isRecurrence = 1 setzen wenn für die aktuelle Originalstartzeit ($bts) eine RECURRENCEID vorliegt -> Veränderung ist vorhanden
|
|
next if(!$data{SSCal}{$name}{vcalendar}{"$uid"}{VALM}{RECURRENCEID}{$_});
|
|
$isRecurrence = 1 if($data{SSCal}{$name}{vcalendar}{"$uid"}{VALM}{RECURRENCEID}{$_} eq $origdtstart);
|
|
}
|
|
|
|
my $l = length (keys %{$data{SSCal}{$name}{vcalendar}{"$uid"}{VALM}{TIMEVALUE}}); # Anzahl Stellen (Länge) des aktuellen VALM TIMEVALUE Hashes
|
|
my $ens = 0;
|
|
|
|
for (keys %{$data{SSCal}{$name}{vcalendar}{"$uid"}{VALM}{TIMEVALUE}}) {
|
|
my $z = $_;
|
|
$val = encode("UTF-8", $data{SSCal}{$name}{vcalendar}{"$uid"}{VALM}{TIMEVALUE}{$z});
|
|
|
|
if(!$data{SSCal}{$name}{vcalendar}{"$uid"}{VALM}{RECURRENCEID}{$z} && !$isRecurrence) { # wenn keine Veränderung vorhanden ist ({RECURRENCEID}{index}=undef) gelten die Erinnerungszeiten Standarderinnerungszeiten
|
|
($uts,$td) = evtNotTime ($name,$val,$bts);
|
|
push(@$aref, $bts+$n." 80_".sprintf("%0$l.0f", $ens)."_notifyDateTime " .$td."\n");
|
|
|
|
$alarmed = isAlarmed ($ts,$uts,$bts) if(!$alarmed);
|
|
}
|
|
elsif ($data{SSCal}{$name}{vcalendar}{"$uid"}{VALM}{RECURRENCEID}{$z} &&
|
|
$data{SSCal}{$name}{vcalendar}{"$uid"}{VALM}{RECURRENCEID}{$z} eq $origdtstart) {
|
|
my ($y, $m, $d) = $bdate =~ /^(\d{4})-(\d{2})-(\d{2})/x; # Timestamp für Berechnung Erinnerungszeit Begindatum/Zeit ...
|
|
my ($h, $mi, $s) = $btime =~ /^(\d{2}):(\d{2}):(\d{2})/x;
|
|
|
|
eval { $chts = fhemTimeLocal($s, $mi, $h, $d, $m-1, $y-1900); # ... neu aus bdate/btime ableiten wegen Änderung durch Recurrance-id
|
|
1;
|
|
} or do {
|
|
my $err = (split(" at", $@))[0];
|
|
Log3($name, 3, "$name - ERROR - invalid date/time format in 'writeValuesToArray' detected: $err ");
|
|
};
|
|
|
|
($uts,$td) = evtNotTime ($name,$val,$chts);
|
|
push(@$aref, $bts+$n." 80_".sprintf("%0$l.0f", $ens)."_notifyDateTime " .$td."\n");
|
|
|
|
$alarmed = isAlarmed ($ts,$uts,$chts) if(!$alarmed);
|
|
|
|
$isAlldaychanded = 0;
|
|
}
|
|
|
|
$ens++;
|
|
}
|
|
|
|
# restliche Keys extrahieren
|
|
for my $p (keys %{$vh}) {
|
|
$vh->{$p} = "" if(!defined $vh->{$p});
|
|
$vh->{$p} = jboolmap ($vh->{$p}, "bin"); # boolsche Werte binär dekodieren
|
|
next if($vh->{$p} eq "");
|
|
|
|
$val = encode("UTF-8", $vh->{$p});
|
|
|
|
push(@$aref, $bts+$n." 01_Summary " .$val."\n") if($p eq "summary");
|
|
push(@$aref, $bts+$n." 03_Description " .$val."\n") if($p eq "description");
|
|
push(@$aref, $bts+$n." 35_Location " .$val."\n") if($p eq "location");
|
|
|
|
if($p eq "gps") {
|
|
my ($address,$lng,$lat) = ("","","");
|
|
for my $r (keys %{$vh->{gps}}) {
|
|
$vh->{$p}{$r} = "" if(!defined $vh->{$p}{$r});
|
|
next if($vh->{$p}{$r} eq "");
|
|
if ($r eq "address") {
|
|
$address = encode("UTF-8", $vh->{$p}{$r}) if($vh->{$p}{$r});
|
|
}
|
|
if ($r eq "gps") {
|
|
$lng = encode("UTF-8", $vh->{$p}{$r}{lng});
|
|
$lat = encode("UTF-8", $vh->{$p}{$r}{lat});
|
|
}
|
|
}
|
|
push(@$aref, $bts+$n." 40_gpsAddress " .$address."\n");
|
|
$val = "lat=".$lat.",lng=".$lng;
|
|
push(@$aref, $bts+$n." 45_gpsCoordinates " .$val."\n");
|
|
}
|
|
|
|
push(@$aref, $bts+$n." 50_isAllday " .(defined $isAlldaychanded ? $isAlldaychanded : $val)."\n") if($p eq "is_all_day");
|
|
push(@$aref, $bts+$n." 55_isRepeatEvt " .$val."\n") if($p eq "is_repeat_evt");
|
|
|
|
if($p eq "due") {
|
|
my (undef,undef,$duedate,$duetime,$duets,undef) = explodeDateTime ($hash, $val, 0, 0, 0);
|
|
push(@$aref, $bts+$n." 60_dueDateTime " .$duedate." ".$duetime."\n");
|
|
push(@$aref, $bts+$n." 65_dueTimestamp " .$duets."\n");
|
|
}
|
|
|
|
push(@$aref, $bts+$n." 85_percentComplete " .$val."\n") if($p eq "percent_complete" && $om eq "todolist");
|
|
push(@$aref, $bts+$n." 90_calName " .getCalFromId($hash,$val)."\n") if($p eq "original_cal_id");
|
|
|
|
if($p eq "evt_repeat_setting") {
|
|
for my $r (keys %{$vh->{evt_repeat_setting}}) {
|
|
$vh->{$p}{$r} = "" if(!defined $vh->{$p}{$r});
|
|
next if($vh->{$p}{$r} eq "");
|
|
$val = encode("UTF-8", $vh->{$p}{$r});
|
|
push(@$aref, $bts+$n." 70_repeatRule ".$val."\n") if($r eq "repeat_rule");
|
|
}
|
|
}
|
|
|
|
push(@$aref, $bts+$n." 95_IcalUID " .$val."\n") if($p eq "ical_uid");
|
|
push(@$aref, $bts+$n." 98_EventId " .$val."\n") if($p eq "evt_id");
|
|
}
|
|
|
|
$status = "upcoming" if($upcoming);
|
|
$status = "alarmed" if($alarmed);
|
|
$status = "started" if($started);
|
|
$status = "ended" if($ended);
|
|
|
|
push(@$aref, $bts+$n." 17_Status " .$status."\n");
|
|
push(@$aref, $bts+$n." 99_---------------------- " ."--------------------------------------------------------------------"."\n");
|
|
|
|
return;
|
|
}
|
|
|
|
#############################################################################################
|
|
# - füllt zentrales $data{SSCal}{$name}{eventlist} Valuehash
|
|
# - erstellt Readings aus $data{SSCal}{$name}{eventlist}
|
|
# - ruft Routine auf um zusätzliche Steuerungsevents zu erstellen
|
|
#############################################################################################
|
|
sub createReadings {
|
|
my ($string) = @_;
|
|
my @a = split("\\|",$string);
|
|
my $name = $a[0];
|
|
my $hash = $defs{$name};
|
|
my $rowlist = $a[1] ? decode_base64($a[1]) : "";
|
|
|
|
my @abnr;
|
|
|
|
if ($rowlist) {
|
|
my @row_array = split("_ESC_", $rowlist);
|
|
|
|
# zentrales Datenhash füllen (erzeugt dadurch sortierbare Keys)
|
|
for my $row (@row_array) {
|
|
chomp $row;
|
|
my @r = split(" ", $row, 3);
|
|
$data{SSCal}{$name}{eventlist}{$r[0]}{$r[1]} = $r[2];
|
|
}
|
|
}
|
|
|
|
# Readings der Eventliste erstellen
|
|
if($data{SSCal}{$name}{eventlist}) {
|
|
my $l = length(keys %{$data{SSCal}{$name}{eventlist}}); # Anzahl Stellen des max. Index ermitteln
|
|
|
|
readingsBeginUpdate($hash);
|
|
$data{SSCal}{$name}{lstUpdtTs} = $hash->{".updateTime"}; # letzte Updatezeit speichern (Unix Format)
|
|
|
|
my $k = 0;
|
|
for my $idx (sort keys %{$data{SSCal}{$name}{eventlist}}) {
|
|
my $idxstr = sprintf("%0$l.0f", $k); # Blocknummer erstellen
|
|
push(@abnr, $idxstr); # Array aller vorhandener Blocknummern erstellen
|
|
|
|
for my $r (keys %{$data{SSCal}{$name}{eventlist}{$idx}}) {
|
|
if($r =~ /.*Timestamp$/x) { # 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);
|
|
}
|
|
else {
|
|
delReadings($name,0); # alle Kalender-Readings löschen
|
|
}
|
|
|
|
doCompositeEvents ($name,\@abnr,$data{SSCal}{$name}{eventlist}); # spezifische Controlevents erstellen
|
|
|
|
checkSendRetry ($name, 0, $queueStartFn);
|
|
|
|
$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", FmtTime(time));
|
|
readingsBulkUpdate ($hash, "state", "done");
|
|
readingsEndUpdate ($hash,1);
|
|
|
|
delReadings($name,1) if($data{SSCal}{$name}{lstUpdtTs}); # Readings löschen wenn Timestamp nicht "lastUpdate"
|
|
|
|
if(AttrVal($name, "createATDevs", 0)) {
|
|
createAtDevices ($name,\@abnr,$data{SSCal}{$name}{eventlist}); # automatisch at-Devics mit FHEM/Perl-Kommandos erstellen
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
#############################################################################################
|
|
# 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 doCompositeEvents {
|
|
my ($name,$abnr,$evref) = @_;
|
|
my $hash = $defs{$name};
|
|
|
|
my ($summary,$desc,$begin,$status,$isrepeat,$id,$event);
|
|
|
|
if(@{$abnr}) {
|
|
my $nrs = join(" ", @{$abnr});
|
|
$event = "compositeBlockNumbers: $nrs";
|
|
}
|
|
else {
|
|
$event = "compositeBlockNumbers: none";
|
|
}
|
|
CommandTrigger(undef, "$name $event");
|
|
|
|
for my $bnr (@{$abnr}) {
|
|
$summary = ReadingsVal($name, $bnr."_01_Summary", "");
|
|
$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", "");
|
|
|
|
$begin =~ s/\s/T/x; # Formatierung nach ISO8601 (YYYY-MM-DDTHH:MM:SS) für at-Device
|
|
|
|
if($begin) { # einen Composite-Event erstellen wenn Beginnzeit gesetzt ist
|
|
$event = "composite: $bnr $id $isrepeat $begin $status ".($desc?$desc:$summary);
|
|
CommandTrigger(undef, "$name $event");
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
#############################################################################################
|
|
# erstellt automatisch AT-Devices aus Kalendereinträgen die FHEM-Befehle oder
|
|
# Perl-Routinen in "Description" enthalten.
|
|
# 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 createAtDevices {
|
|
my ($name,$abnr,$evref) = @_;
|
|
my $hash = $defs{$name};
|
|
|
|
my ($desc,$begin,$status,$isrepeat,$id,@devs,$err,$summary,$location);
|
|
|
|
my $room = AttrVal($name, "room", "");
|
|
my $assoc = "";
|
|
readingsDelete($hash,".associatedWith"); # Deviceassoziationen löschen
|
|
|
|
@devs = devspec2array("TYPE=at:FILTER=NAME=SSCal.$name.*");
|
|
for (@devs) {
|
|
next if(!$defs{$_});
|
|
Log3($name, 4, "$name - delete device: $_");
|
|
CommandDelete(undef,$_);
|
|
}
|
|
|
|
for my $bnr (@{$abnr}) {
|
|
$summary = ReadingsVal($name, $bnr."_01_Summary", "");
|
|
$desc = ReadingsVal($name, $bnr."_03_Description", "");
|
|
$begin = ReadingsVal($name, $bnr."_05_Begin", "");
|
|
$status = ReadingsVal($name, $bnr."_17_Status", "");
|
|
$location = ReadingsVal($name, $bnr."_35_Location", $room); # Location wird als room gesetzt
|
|
$id = ReadingsVal($name, $bnr."_98_EventId", "");
|
|
|
|
if($begin && $status =~ /upcoming|alarmed/x && $desc =~ /^\s*\{(.*)\}\s*$/xs) { # ein at-Device erstellen wenn Voraussetzungen erfüllt
|
|
my $cmd = $1;
|
|
$begin =~ s/\s/T/x; # Formatierung nach ISO8601 (YYYY-MM-DDTHH:MM:SS) für at-Devices
|
|
my $ao = $begin;
|
|
$ao =~ s/[-:]//gx;
|
|
my $atn = "SSCal.$name.$id.$ao"; # Name neues at-Device
|
|
|
|
Log3($name, 4, "$name - Command detected. Create device \"$atn\" with type \"at\".");
|
|
$err = CommandDefine(undef, "$atn at $begin $cmd");
|
|
|
|
if ($err) {
|
|
Log3($name, 1, "$name - Error during create \"$atn\": $err");
|
|
}
|
|
else {
|
|
CommandSetReading(undef, "$atn .associatedWith $name");
|
|
CommandAttr(undef,"$atn room $location");
|
|
CommandAttr(undef,"$atn alias $summary");
|
|
CommandAttr(undef,"$atn comment created automatically by SSCal \"$name\" ");
|
|
$assoc .= " $atn";
|
|
}
|
|
}
|
|
}
|
|
|
|
CommandSetReading(undef, "$name .associatedWith $assoc");
|
|
|
|
return;
|
|
}
|
|
|
|
####################################################################################################
|
|
# Abbruchroutine BlockingCall
|
|
####################################################################################################
|
|
sub 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");
|
|
|
|
checkSendRetry ($name, 0, $queueStartFn);
|
|
|
|
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 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);
|
|
}
|
|
|
|
#############################################################################################
|
|
# extrahiere Key/Value Paare des VCALENDAR
|
|
# $vh = Referenz zum Kalenderdatenhash
|
|
#
|
|
# Ergebis Hash:
|
|
# {
|
|
# 'RECURRENCEID' => {
|
|
# '0' => undef,
|
|
# '2' => 'TZID=Europe/Berlin:20200222T191500 ',
|
|
# '1' => 'TZID=Europe/Berlin:20200219T191500 '
|
|
# },
|
|
# 'DTSTART' => {
|
|
# '0' => 'TZID=Europe/Berlin:20200218T191500',
|
|
# '1' => 'TZID=Europe/Berlin:20200219T191800',
|
|
# '2' => 'TZID=Europe/Berlin:20200222T192000'
|
|
# },
|
|
# 'EXDATES' => {
|
|
# '1' => undef,
|
|
# '2' => undef,
|
|
# '0' => '20200221T191500 20200220T191500 '
|
|
# },
|
|
# 'SEQUENCE' => {
|
|
# '0' => '4',
|
|
# '2' => '5',
|
|
# '1' => '5'
|
|
# }
|
|
# }
|
|
#
|
|
# Auswertung mit $data{SSCal}{$name}{vcalendar}{"$uid"}
|
|
#
|
|
#############################################################################################
|
|
sub extractIcal { ## no critic 'complexity'
|
|
my ($name,$vh) = @_;
|
|
my $hash = $defs{$name};
|
|
|
|
my %vcal;
|
|
my %valm;
|
|
my %icals;
|
|
my ($uid,$k,$v,$n);
|
|
|
|
if($vh->{evt_ical}) {
|
|
my @ical = split(/\015\012/x, $vh->{evt_ical});
|
|
|
|
my $do = 0;
|
|
$n = 0;
|
|
for (@ical) {
|
|
if($_ =~ m/^([-A-Z]*;)/x) {
|
|
($k,$v) = split(";", $_, 2);
|
|
}
|
|
else {
|
|
($k,$v) = split(":", $_, 2);
|
|
}
|
|
|
|
$v = "" if(!$v);
|
|
if("$k:$v" eq "BEGIN:VEVENT") {$do = 1;};
|
|
if("$k:$v" eq "END:VEVENT") {$do = 0; $n++;};
|
|
|
|
if ($do) {
|
|
$vcal{$n}{UID} = $v if($k eq "UID");
|
|
$vcal{$n}{SEQUENCE} = $v if($k eq "SEQUENCE");
|
|
|
|
if($k eq "DTSTART") {
|
|
$v = icalTimecheck ($name,$v);
|
|
$vcal{$n}{DTSTART} = $v;
|
|
}
|
|
|
|
if($k eq "DTEND") {
|
|
$v = icalTimecheck ($name,$v);
|
|
$vcal{$n}{DTEND} = $v;
|
|
}
|
|
|
|
if($k eq "RECURRENCE-ID") {
|
|
$v = icalTimecheck ($name,$v);
|
|
$vcal{$n}{RECURRENCEID} = $v;
|
|
}
|
|
|
|
if($k eq "EXDATE") {
|
|
$v = icalTimecheck ($name,$v);
|
|
$vcal{$n}{EXDATES} .= $v." ";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$n = 0;
|
|
while ($vh->{evt_notify_setting}[$n]) {
|
|
for (keys %{$vh->{evt_notify_setting}[$n]}) {
|
|
if($_ eq "recurrence-id") {
|
|
$valm{$n}{RECURRENCEID} = icalTimecheck ($name,$vh->{evt_notify_setting}[$n]{$_});
|
|
}
|
|
|
|
if($_ eq "time_value") {
|
|
$valm{$n}{TIMEVALUE} = $vh->{evt_notify_setting}[$n]{$_};
|
|
}
|
|
}
|
|
$n++;
|
|
}
|
|
|
|
$n = 0;
|
|
# VCALENDER Einträge konsolidieren
|
|
while ($vcal{$n}) {
|
|
$uid = $vcal{$n}{UID};
|
|
$icals{$uid}{SEQUENCE}{$n} = $vcal{$n}{SEQUENCE};
|
|
$icals{$uid}{DTSTART}{$n} = $vcal{$n}{DTSTART};
|
|
$icals{$uid}{DTEND}{$n} = $vcal{$n}{DTEND};
|
|
$icals{$uid}{EXDATES}{$n} = trim($vcal{$n}{EXDATES});
|
|
$icals{$uid}{RECURRENCEID}{$n} = $vcal{$n}{RECURRENCEID};
|
|
$n++;
|
|
}
|
|
|
|
$n = 0;
|
|
# VALARM Einträge konsolidieren
|
|
$uid = $vh->{ical_uid};
|
|
while ($valm{$n}) {
|
|
$icals{$uid}{VALM}{RECURRENCEID}{$n} = $valm{$n}{RECURRENCEID};
|
|
$icals{$uid}{VALM}{TIMEVALUE}{$n} = $valm{$n}{TIMEVALUE};
|
|
$n++;
|
|
}
|
|
|
|
$data{SSCal}{$name}{vcalendar} = \%icals; # Achtung: bei asynch Mode ist $data{SSCal}{$name}{vcalendar} nur im BlockingCall verfügbar !!
|
|
|
|
Log3($name, 5, "$name - VCALENDAR extract of UID \"$uid\":\n".Dumper $data{SSCal}{$name}{vcalendar}{"$uid"});
|
|
|
|
return;
|
|
}
|
|
|
|
#############################################################################################
|
|
# Checked und korrigiert Zeitformate aus VCALENDAR um sie mit API-Werten vergleichbar
|
|
# zu machen
|
|
#############################################################################################
|
|
sub icalTimecheck {
|
|
my $name = shift;
|
|
my $v = shift;
|
|
|
|
return if(!$v);
|
|
|
|
my ($sec,$min,$hour,$mday,$month,$year,$zulu,$tstamp,$d,$t,$isdst,$tz);
|
|
|
|
$zulu = 0;
|
|
$v = (split(":", $v))[-1] if($v =~ /:/x);
|
|
$zulu = 3600 if($v =~ /Z$/x); # Zulu-Zeit wenn EXDATE mit "Z" endet -> +1 Stunde
|
|
|
|
($d,$t) = split("T", $v);
|
|
$year = substr($d,0,4);
|
|
$month = substr($d,4,2);
|
|
$mday = substr($d,6,2);
|
|
|
|
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";
|
|
}
|
|
|
|
eval { $tstamp = fhemTimeLocal($sec, $min, $hour, $mday, $month-1, $year-1900);
|
|
1;
|
|
} or do {
|
|
my $err = (split(" at", $@))[0];
|
|
Log3($name, 3, "$name - ERROR - invalid date/time format in 'icalTimecheck' detected: $err ");
|
|
};
|
|
$isdst = (localtime($tstamp))[8];
|
|
$zulu = 7200 if($isdst && $zulu); # wenn Sommerzeit und Zulu-Zeit -> +1 Stunde
|
|
$tstamp += $zulu;
|
|
$v = strftime "%Y%m%dT%H%M%S", localtime($tstamp);
|
|
|
|
return $v;
|
|
}
|
|
|
|
#############################################################################################
|
|
# Ist Event bevorstehend ?
|
|
# Rückkehrwert 1 wenn aktueller Timestamp $ts vor Alarmzeit $ats und vor Startzeit $bts,
|
|
# sonst 0
|
|
#############################################################################################
|
|
sub 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 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 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 isEnded {
|
|
my ($ts,$ets) = @_;
|
|
|
|
return 0 unless($ets && $ts);
|
|
|
|
return $ets <= $ts ? 1 : 0;
|
|
}
|
|
|
|
#############################################################################################
|
|
# check SID
|
|
#############################################################################################
|
|
sub checkSID {
|
|
my $name = shift;
|
|
my $hash = $defs{$name};
|
|
|
|
# SID holen bzw. login
|
|
my $subref = \&calOp;
|
|
|
|
if(!$hash->{HELPER}{SID}) {
|
|
Log3($name, 3, "$name - no session ID found - get new one");
|
|
login($hash, $data{SSCal}{$name}{calapi}, $subref, $name, $splitstr);
|
|
return;
|
|
}
|
|
|
|
return calOp($name);
|
|
}
|
|
|
|
#############################################################################################
|
|
# Start- und Endezeit ermitteln
|
|
#############################################################################################
|
|
sub 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) = 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) = 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'
|
|
# 'time_value' => 'PT6H'
|
|
# 'time_value' => '-P1DT15H'
|
|
#
|
|
# Rückgabe: $uts: Unix-Timestamp
|
|
# $ts: Timstamp als YYYY-MM-DD HH:MM:SS
|
|
#
|
|
#############################################################################################
|
|
sub evtNotTime {
|
|
my ($name,$tv,$bts) = @_;
|
|
my $hash = $defs{$name};
|
|
my ($uts,$ts) = ("","");
|
|
my ($corr);
|
|
|
|
return ("","") if(!$tv || !$bts);
|
|
|
|
if($tv =~ /^-P(\d)+D$/x) {
|
|
$corr = -1*$1*86400;
|
|
}
|
|
elsif ($tv =~ /^-PT(\d+)H$/x) {
|
|
$corr = -1*$1*3600;
|
|
}
|
|
elsif ($tv =~ /^-PT(\d+)M$/x) {
|
|
$corr = -1*$1*60;
|
|
}
|
|
elsif ($tv =~ /^PT(\d+)S$/x) {
|
|
$corr = $1;
|
|
}
|
|
elsif ($tv =~ /^PT(\d+)M$/x) {
|
|
$corr = $1*60;
|
|
}
|
|
elsif ($tv =~ /^PT(\d+)H$/x) {
|
|
$corr = $1*3600;
|
|
}
|
|
elsif ($tv =~ /^-P(\d)+DT(\d+)H$/x) {
|
|
$corr = -1*($1*86400 + $2*3600);
|
|
}
|
|
|
|
if(defined $corr) {
|
|
$uts = $bts+$corr;
|
|
$ts = FmtDateTime($uts);
|
|
}
|
|
|
|
return ($uts,$ts);
|
|
}
|
|
|
|
#############################################################################################
|
|
# Unix timestamp aus Zeitdifferenz berechnen
|
|
#############################################################################################
|
|
sub GetSecondsFromTimeSpec {
|
|
my ($tspec) = @_;
|
|
|
|
# days
|
|
if($tspec =~ m/^([0-9]+)d$/x) {
|
|
return ("", $1*86400);
|
|
}
|
|
|
|
# seconds
|
|
if($tspec =~ m/^([0-9]+)s?$/x) {
|
|
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])$/x) {
|
|
return ("", $4+60*($3+60*($2+24*$1)));
|
|
}
|
|
|
|
# HH:MM:SS
|
|
if($tspec =~ m/^([0-9]+):([0-5][0-9]):([0-5][0-9])$/x) {
|
|
return ("", $3+60*($2+(60*$1)));
|
|
}
|
|
|
|
# HH:MM
|
|
if($tspec =~ m/^([0-9]+):([0-5][0-9])$/x) {
|
|
return ("", 60*($2+60*$1));
|
|
}
|
|
|
|
return ("Wrong time specification $tspec", undef);
|
|
}
|
|
|
|
################################################################
|
|
# Kalendername aus Kalender-Id liefern
|
|
################################################################
|
|
sub getCalFromId {
|
|
my ($hash,$cid) = @_;
|
|
my $cal = "";
|
|
$cid = trim($cid);
|
|
|
|
for my $calname (keys %{$hash->{HELPER}{CALENDARS}}) {
|
|
my $oid = $hash->{HELPER}{CALENDARS}{"$calname"}{id};
|
|
next if(!$oid);
|
|
$oid = trim($oid);
|
|
if($oid eq $cid) {
|
|
$cal = $calname;
|
|
last;
|
|
}
|
|
}
|
|
|
|
return $cal;
|
|
}
|
|
|
|
################################################################
|
|
# addiert Anzahl ($n) Sekunden ($s) zu $t1
|
|
################################################################
|
|
sub plusNSeconds {
|
|
my ($t1, $s, $n) = @_;
|
|
|
|
$n = 1 unless defined($n);
|
|
my $t2 = $t1+$n*$s;
|
|
|
|
return $t2;
|
|
}
|
|
|
|
#############################################################################################
|
|
# 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 !)
|
|
# $dtstart: man benötigt originales DTSTART für den Vergleich bei Recurring Terminen
|
|
#############################################################################################
|
|
sub explodeDateTime { ## no critic 'complexity'
|
|
my ($hash,$dt,$isallday,$uid,$dtstart) = @_;
|
|
my $name = $hash->{NAME};
|
|
my ($tz,$t) = ("","");
|
|
my ($d,$tstamp) = ("",0);
|
|
my $invalid = 0;
|
|
my $corrsec = 0; # Korrektursekunde
|
|
my $excl = 0; # 1 wenn der Eintrag exkludiert werden soll
|
|
|
|
my ($sec,$min,$hour,$mday,$month,$year,$checkbegin,$changed,$changet,$changedt,$z);
|
|
|
|
return ($invalid,$tz,$d,$t,$tstamp,$excl) if(!$dt);
|
|
|
|
$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)
|
|
|
|
if($dt =~ /^TZID=.*$/x) {
|
|
($tz,$dt) = split(":", $dt);
|
|
$tz = (split("=", $tz))[1];
|
|
}
|
|
|
|
if($dtstart) {
|
|
$dtstart = (split(":", $dtstart))[-1] if($dtstart =~ /:/x);
|
|
|
|
# check ob recurring date excluded werden muss (Serienelement gelöscht)
|
|
my $exdates = $data{SSCal}{$name}{vcalendar}{"$uid"}{EXDATES}{0};
|
|
my %seen;
|
|
if($exdates) {
|
|
my @exd = split(" ", $exdates);
|
|
# grep { !$seen{$_}++ } @exd;
|
|
for (@exd) { $seen{$_}++ if(!$seen{$_}) };
|
|
}
|
|
$excl = 1 if($seen{$dtstart}); # check erfolgreich -> exclude recurring date weil (Serienelement gelöscht)
|
|
|
|
# prüfen ob Serienelement verändert wurde
|
|
if($dt eq $dtstart) {
|
|
$checkbegin = 1
|
|
}
|
|
else {
|
|
$checkbegin = 0
|
|
};
|
|
|
|
if ($checkbegin) {
|
|
# prüfen ob DTSTART verändert
|
|
for (keys %{$data{SSCal}{$name}{vcalendar}{"$uid"}{RECURRENCEID}}) {
|
|
next if(!$data{SSCal}{$name}{vcalendar}{"$uid"}{RECURRENCEID}{$_});
|
|
$z = $_ if($data{SSCal}{$name}{vcalendar}{"$uid"}{RECURRENCEID}{$_} eq $dtstart);
|
|
}
|
|
if($z) {
|
|
$changedt = $data{SSCal}{$name}{vcalendar}{"$uid"}{DTSTART}{$z};
|
|
my ($y, $m, $dd, $h, $mi, $s) = $changedt =~ /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})/x;
|
|
$changed = $y."-".$m."-".$dd; # einmalig geändertes Datum
|
|
$changet = $h.":".$mi.":".$s; # einmalig geänderte Zeit
|
|
}
|
|
}
|
|
else { # prüfen ob DTEND verändert
|
|
for (keys %{$data{SSCal}{$name}{vcalendar}{"$uid"}{RECURRENCEID}}) {
|
|
next if(!$data{SSCal}{$name}{vcalendar}{"$uid"}{RECURRENCEID}{$_});
|
|
$z = $_ if($data{SSCal}{$name}{vcalendar}{"$uid"}{RECURRENCEID}{$_} eq $dtstart);
|
|
}
|
|
if($z) {
|
|
$changedt = $data{SSCal}{$name}{vcalendar}{"$uid"}{DTEND}{$z};
|
|
my ($y, $m, $dd, $h, $mi, $s) = $changedt =~ /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})/x;
|
|
$changed = $y."-".$m."-".$dd; # einmalig geändertes Datum
|
|
$changet = $h.":".$mi.":".$s; # einmalig geänderte Zeit
|
|
}
|
|
}
|
|
}
|
|
|
|
($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})\s(\d{2}):(\d{2}):(\d{2})/x) {
|
|
Log3($name, 2, "$name - ERROR - invalid DateTime format for explodeDateTime: $d $t");
|
|
}
|
|
|
|
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);
|
|
1;
|
|
} or do {
|
|
my $err = (split(" at", $@))[0];
|
|
Log3($name, 3, "$name - WARNING - invalid date/time format detected: $err. It will be ignored.");
|
|
$invalid = 1;
|
|
};
|
|
|
|
if(!$invalid) {
|
|
$tstamp -= $corrsec;
|
|
my $nt = FmtDateTime($tstamp);
|
|
($year, $month, $mday, $hour, $min, $sec) = $nt =~ /^(\d{4})-(\d{2})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})/x;
|
|
$d = $year."-".$month."-".$mday;
|
|
$t = $hour.":".$min.":".$sec;
|
|
}
|
|
}
|
|
|
|
eval { $tstamp = fhemTimeLocal($sec, $min, $hour, $mday, $month-1, $year-1900);
|
|
1;
|
|
} or do {
|
|
my $err = (split(" at", $@))[0];
|
|
Log3($name, 3, "$name - WARNING - invalid format of recurring event: $err. It will be ignored due to RFC 5545 standard.");
|
|
$invalid = 1;
|
|
};
|
|
|
|
$d = $changed // $d; # mit einmalig geänderten Datum ersetzen
|
|
$t = $changet // $t; # mit einmalig geänderter Zeit ersetzen
|
|
|
|
return ($invalid,$tz,$d,$t,$tstamp,$excl);
|
|
}
|
|
|
|
#############################################################################################
|
|
# Kalenderliste als HTML-Tabelle zurückgeben
|
|
#############################################################################################
|
|
sub calAsHtml { ## no critic 'complexity'
|
|
my ($name,$FW_wname) = @_;
|
|
my $hash = $defs{$name};
|
|
my $lang = AttrVal("global", "language", "EN");
|
|
my $mi = AttrVal($name, "tableColumnMap", "icon");
|
|
|
|
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);
|
|
my ($colSymbolAlign,$colBeginAlign,$colEndAlign,$colDayAlign,$colDLongAlign,$colWeekdayAlign,$colTzAlign,$colSummaryAlign,$colDescAlign,$colStatusAlign,$colCompAlign,$colLocAlign,$colMapAlign,$colCalAlign,$colIdAlign);
|
|
|
|
# alle Readings in Array einlesen
|
|
my @allrds = keys%{$defs{$name}{READINGS}};
|
|
|
|
# Sprachsteuerung
|
|
my $de = 0;
|
|
if($lang eq "DE") {$de = 1};
|
|
|
|
# Entscheidung ob Tabelle für Small Screen optimiert
|
|
my $small = 0;
|
|
if ($FW_wname && $hash->{HELPER}{tableSpecs}{smallScreenStyles}) { # Aufruf durch FHEMWEB und smallScreen-Eigenschaft gesetzt
|
|
my %specs;
|
|
my $FW_style = AttrVal($FW_wname, "stylesheetPrefix", "default");
|
|
my @scspecs = split(",", $hash->{HELPER}{tableSpecs}{smallScreenStyles}); # Eigenschaft smallScreen in Array lesen
|
|
# grep { !$specs{$_}++ } @scspecs;
|
|
for (@scspecs) { $specs{$_}++ if(!$specs{$_}) };
|
|
$small = 1 if($specs{$FW_style}); # Tabelle für small-Style anpassen
|
|
}
|
|
|
|
# Auswahl der darzustellenden Tabellenfelder
|
|
my %seen;
|
|
my @cof = split(",", AttrVal($name, "tableFields", "Begin,End,Summary,Status,Location"));
|
|
grep { !$seen{$_}++ } @cof;
|
|
|
|
# Gestaltung Headerzeile
|
|
my $nohead = 0; # Unterdrückung Anzeige Headerzeile: 0 - nein, 1 - Ja
|
|
eval { $nohead = evalTableSpecs ( {
|
|
href => $hash,
|
|
def => $nohead,
|
|
base => $hash->{HELPER}{tableSpecs}{cellStyle}{noHeader},
|
|
bnr => "",
|
|
readref => \@allrds,
|
|
rdtype => "string"
|
|
}
|
|
); 1;
|
|
} or do {
|
|
Log3($name, 1, "$name - Syntax error in attribute \"tableSpecs\" near \"cellStyle\": $@");
|
|
};
|
|
|
|
my $headalign = "center"; # Ausrichtung der Headerzeile, default: center
|
|
eval { $headalign = evalTableSpecs ( {
|
|
href => $hash,
|
|
def => $headalign,
|
|
base => $hash->{HELPER}{tableSpecs}{cellStyle}{headerAlign},
|
|
bnr => "",
|
|
readref => \@allrds,
|
|
rdtype => "string"
|
|
}
|
|
); 1;
|
|
} or do {
|
|
Log3($name, 1, "$name - Syntax error in attribute \"tableSpecs\" near \"cellStyle\": $@");
|
|
};
|
|
|
|
$headalign = "cal".$headalign;
|
|
|
|
# Tabelle erstellen
|
|
my $out = "<html>";
|
|
$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>";
|
|
$out .= "<style>TD.calleft {text-align: left;} </style>";
|
|
$out .= "<style>TD.calcenter {text-align: center;} </style>";
|
|
$out .= "<style>TD.calw150 {width: 150px;} </style>";
|
|
|
|
# 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'>";
|
|
|
|
$out .= "<table class='block'>";
|
|
|
|
# Tabellenheader
|
|
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>";
|
|
}
|
|
|
|
my $maxbnr;
|
|
for my $key (keys %{$defs{$name}{READINGS}}) {
|
|
my ($bno) = $key =~ /^(\d+)_\d+_EventId$/x;
|
|
next if(!defined $bno);
|
|
$maxbnr = $bno if(!$maxbnr || $bno > $maxbnr);
|
|
}
|
|
|
|
return "" if(!defined $maxbnr);
|
|
|
|
my $l = length($maxbnr);
|
|
|
|
my $k;
|
|
for ($k=0;$k<=$maxbnr;$k++) {
|
|
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
|
|
|
|
($begind,$begint,$endd,$endt,$gps) = ("","","","","");
|
|
|
|
# Readings auslesen
|
|
$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", "");
|
|
$dleftlong = ReadingsVal($name, $bnr."_25_daysLeftLong", "");
|
|
$weekday = ReadingsVal($name, $bnr."_30_Weekday", "");
|
|
$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", "");
|
|
|
|
if($gpsc) {
|
|
my $micon;
|
|
if ($mi eq "icon") {
|
|
# Karten-Icon auswählen
|
|
$di = "it_i-net";
|
|
eval { $micon = evalTableSpecs ( {
|
|
href => $hash,
|
|
def => $di,
|
|
base => $hash->{HELPER}{tableSpecs}{columnMapIcon},
|
|
bnr => $bnr,
|
|
readref => \@allrds,
|
|
rdtype => "image"
|
|
}
|
|
); 1;
|
|
} or do {
|
|
Log3($name, 1, "$name - Syntax error in attribute \"tableSpecs\" near \"columnMapIcon\": $@")
|
|
};
|
|
}
|
|
elsif ($mi eq "data") {
|
|
$micon = join(" ", split(",", $gpsc));
|
|
}
|
|
elsif ($mi eq "text") { # Karten-Text auswählen
|
|
my $dt = "link";
|
|
eval { $micon = evalTableSpecs ( {
|
|
href => $hash,
|
|
def => $dt,
|
|
base => $hash->{HELPER}{tableSpecs}{columnMapText},
|
|
bnr => $bnr,
|
|
readref => \@allrds,
|
|
rdtype => "string"
|
|
}
|
|
); 1;
|
|
} or do {
|
|
Log3($name, 1, "$name - Syntax error in attribute \"tableSpecs\" near \"columnMapText\": $@");
|
|
};
|
|
}
|
|
else {
|
|
$micon = "";
|
|
}
|
|
|
|
my ($lat,$lng) = split(",", $gpsc);
|
|
$lat = (split("=", $lat))[1];
|
|
$lng = (split("=", $lng))[1];
|
|
|
|
# Kartenanbieter auswählen
|
|
my $up = "GoogleMaps";
|
|
eval { $up = evalTableSpecs ( {
|
|
href => $hash,
|
|
def => $up,
|
|
base => $hash->{HELPER}{tableSpecs}{columnMapProvider},
|
|
bnr => $bnr,
|
|
readref => \@allrds,
|
|
rdtype => "string"
|
|
}
|
|
); 1;
|
|
} or do {
|
|
Log3($name, 1, "$name - Syntax error in attribute \"tableSpecs\" near \"columnMapProvider\": $@");
|
|
};
|
|
|
|
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") {
|
|
$gps = "<a href='https://www.openstreetmap.org/?mlat=$lat&mlon=$lng&zoom=14' target='_blank'> $micon </a>"; # Kartenprovider: OpenstreetMap
|
|
}
|
|
}
|
|
|
|
if($begin ne "") { # Datum sprachabhängig konvertieren bzw. heute/morgen setzen
|
|
my ($ny,$nm,$nd,undef) = split(/[\s-]/x, TimeNow()); # Jetzt
|
|
my ($by,$bm,$bd,$bt) = split(/[\s-]/x, $begin);
|
|
my ($ey,$em,$ed,$et) = split(/[\s-]/x, $end);
|
|
my $ntimes = fhemTimeLocal(00, 00, 00, $nd, $nm-1, $ny-1900);
|
|
my $btimes = fhemTimeLocal(00, 00, 00, $bd, $bm-1, $by-1900);
|
|
my $etimes = fhemTimeLocal(00, 00, 00, $ed, $em-1, $ey-1900);
|
|
|
|
if($de) {
|
|
$begind = "$bd.$bm.$by";
|
|
$endd = "$ed.$em.$ey";
|
|
}
|
|
else {
|
|
$begind = "$by-$bm-$bd";
|
|
$endd = "$ey-$em-$ed";
|
|
}
|
|
my($a,$b,undef) = split(":", $bt);
|
|
$begint = "$a:$b";
|
|
my($c,$d,undef) = split(":", $et);
|
|
$endt = "$c:$d";
|
|
|
|
$edleft = "";
|
|
|
|
if($etimes >= $ntimes) {
|
|
$edleft = int(($etimes - $ntimes)/86400);
|
|
}
|
|
|
|
$begind = (($de)?'heute ':'today ') if($dleft eq "0");
|
|
$endd = (($de)?'heute ':'today ') if($edleft eq "0");
|
|
$begind = (($de)?'morgen ':'tomorrow ') if($dleft eq "1");
|
|
$endd = (($de)?'morgen ':'tomorrow ') if($edleft eq "1");
|
|
|
|
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 = "";
|
|
}
|
|
}
|
|
|
|
# Icon für Spalte Resttage spezifizieren
|
|
eval { $dleft = evalTableSpecs ( {
|
|
href => $hash,
|
|
def => $dleft,
|
|
base => $hash->{HELPER}{tableSpecs}{columnDaysLeftIcon},
|
|
bnr => $bnr,
|
|
readref => \@allrds,
|
|
rdtype => "image"
|
|
}
|
|
); 1;
|
|
} or do {
|
|
Log3($name, 1, "$name - Syntax error in attribute \"tableSpecs\" near \"columnDaysLeftIcon\": $@");
|
|
};
|
|
|
|
# Icon für Spalte Status spezifizieren
|
|
eval { $status = evalTableSpecs ( {
|
|
href => $hash,
|
|
def => $status,
|
|
base => $hash->{HELPER}{tableSpecs}{columnStateIcon},
|
|
bnr => $bnr,
|
|
readref => \@allrds,
|
|
rdtype => "image"
|
|
}
|
|
); 1;
|
|
} or do {
|
|
Log3($name, 1, "$name - Syntax error in attribute \"tableSpecs\" near \"columnStateIcon\": $@");
|
|
};
|
|
|
|
# Icon für Spalte "Symbol" bestimmen
|
|
$di = ($hash->{MODEL} eq "Diary") ? "time_calendar" : "time_note";
|
|
eval { $symbol = evalTableSpecs ( {
|
|
href => $hash,
|
|
def => $di,
|
|
base => $hash->{HELPER}{tableSpecs}{columnSymbolIcon},
|
|
bnr => $bnr,
|
|
readref => \@allrds,
|
|
rdtype => "image"
|
|
}
|
|
); 1;
|
|
} or do {
|
|
Log3($name, 1, "$name - Syntax error in attribute \"tableSpecs\" near \"columnSymbolIcon\": $@");
|
|
};
|
|
|
|
# Gestaltung Spaltentext
|
|
my $coldefalign = "center"; # Ausrichtung der Spalte, default: center
|
|
eval {
|
|
$coldefalign = "cal".evalTableSpecs ( {href => $hash,def => $coldefalign,base => $hash->{HELPER}{tableSpecs}{cellStyle}{columnAlign} ,bnr => "",readref => \@allrds,rdtype => "string"} );
|
|
$colSymbolAlign = "cal".evalTableSpecs ( {href => $hash,def => $coldefalign,base => $hash->{HELPER}{tableSpecs}{cellStyle}{columnSymbolAlign} ,bnr => "",readref => \@allrds,rdtype => "string"} );
|
|
$colBeginAlign = "cal".evalTableSpecs ( {href => $hash,def => $coldefalign,base => $hash->{HELPER}{tableSpecs}{cellStyle}{columnBeginAlign} ,bnr => "",readref => \@allrds,rdtype => "string"} );
|
|
$colEndAlign = "cal".evalTableSpecs ( {href => $hash,def => $coldefalign,base => $hash->{HELPER}{tableSpecs}{cellStyle}{columnEndAlign} ,bnr => "",readref => \@allrds,rdtype => "string"} );
|
|
$colDayAlign = "cal".evalTableSpecs ( {href => $hash,def => $coldefalign,base => $hash->{HELPER}{tableSpecs}{cellStyle}{columnDaysLeftAlign} ,bnr => "",readref => \@allrds,rdtype => "string"} );
|
|
$colDLongAlign = "cal".evalTableSpecs ( {href => $hash,def => $coldefalign,base => $hash->{HELPER}{tableSpecs}{cellStyle}{columnDaysLeftLongAlign} ,bnr => "",readref => \@allrds,rdtype => "string"} );
|
|
$colWeekdayAlign = "cal".evalTableSpecs ( {href => $hash,def => $coldefalign,base => $hash->{HELPER}{tableSpecs}{cellStyle}{columnWeekdayAlign} ,bnr => "",readref => \@allrds,rdtype => "string"} );
|
|
$colTzAlign = "cal".evalTableSpecs ( {href => $hash,def => $coldefalign,base => $hash->{HELPER}{tableSpecs}{cellStyle}{columnTimezoneAlign} ,bnr => "",readref => \@allrds,rdtype => "string"} );
|
|
$colSummaryAlign = "cal".evalTableSpecs ( {href => $hash,def => $coldefalign,base => $hash->{HELPER}{tableSpecs}{cellStyle}{columnSummaryAlign} ,bnr => "",readref => \@allrds,rdtype => "string"} );
|
|
$colDescAlign = "cal".evalTableSpecs ( {href => $hash,def => $coldefalign,base => $hash->{HELPER}{tableSpecs}{cellStyle}{columnDescriptionAlign} ,bnr => "",readref => \@allrds,rdtype => "string"} );
|
|
$colStatusAlign = "cal".evalTableSpecs ( {href => $hash,def => $coldefalign,base => $hash->{HELPER}{tableSpecs}{cellStyle}{columnStatusAlign} ,bnr => "",readref => \@allrds,rdtype => "string"} );
|
|
$colCompAlign = "cal".evalTableSpecs ( {href => $hash,def => $coldefalign,base => $hash->{HELPER}{tableSpecs}{cellStyle}{columnCompletionAlign} ,bnr => "",readref => \@allrds,rdtype => "string"} );
|
|
$colLocAlign = "cal".evalTableSpecs ( {href => $hash,def => $coldefalign,base => $hash->{HELPER}{tableSpecs}{cellStyle}{columnLocationAlign} ,bnr => "",readref => \@allrds,rdtype => "string"} );
|
|
$colMapAlign = "cal".evalTableSpecs ( {href => $hash,def => $coldefalign,base => $hash->{HELPER}{tableSpecs}{cellStyle}{columnMapAlign} ,bnr => "",readref => \@allrds,rdtype => "string"} );
|
|
$colCalAlign = "cal".evalTableSpecs ( {href => $hash,def => $coldefalign,base => $hash->{HELPER}{tableSpecs}{cellStyle}{columnCalendarAlign} ,bnr => "",readref => \@allrds,rdtype => "string"} );
|
|
$colIdAlign = "cal".evalTableSpecs ( {href => $hash,def => $coldefalign,base => $hash->{HELPER}{tableSpecs}{cellStyle}{columnEventIdAlign} ,bnr => "",readref => \@allrds,rdtype => "string"} );
|
|
1;
|
|
} or do {
|
|
Log3($name, 1, "$name - Syntax error in attribute \"tableSpecs\" near \"cellStyle\": $@")
|
|
};
|
|
|
|
my $colalign = $coldefalign;
|
|
|
|
# TabellenBody
|
|
$out .= "<tr class='".($k&1?"odd":"even")."'>";
|
|
$out .= "<td class='cal $colSymbolAlign' > $symbol </td>" if($seen{Symbol});
|
|
if($small) {
|
|
$out .= "<td class='cal $colBeginAlign' > ".$begind." ".$begint. "</td>" if($seen{Begin});
|
|
$out .= "<td class='cal $colEndAlign' > ".$endd ." ".$endt. "</td>" if($seen{End});
|
|
}
|
|
else {
|
|
$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});
|
|
}
|
|
$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});
|
|
$out .= "</tr>";
|
|
}
|
|
|
|
$out .= "</table>";
|
|
$out .= "</td>";
|
|
$out .= "</tr>";
|
|
$out .= "</table>";
|
|
$out .= "</html>";
|
|
|
|
return $out;
|
|
}
|
|
|
|
######################################################################################
|
|
# 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})
|
|
# $allreads: Referenz zum ARRAY was alle vorhandenen Readings des Devices enthält
|
|
# $bnr: Blocknummer Readings
|
|
# $rdtype: erwarteter Datentyp als Rückgabe (image, string)
|
|
#
|
|
######################################################################################
|
|
sub evalTableSpecs { ## no critic 'complexity'
|
|
# my ($hash,$default,$specs,$bnr,$allrds,$rdtype) = @_;
|
|
my ($argref) = @_;
|
|
my $hash = $argref->{href};
|
|
my $default = $argref->{def};
|
|
my $specs = $argref->{base};
|
|
my $bnr = $argref->{bnr};
|
|
my $allrds = $argref->{readref};
|
|
my $rdtype = $argref->{rdtype};
|
|
|
|
my $name = $hash->{NAME};
|
|
my $check;
|
|
|
|
$rdtype = $rdtype // "string"; # "string" als default Rückgabe Datentyp
|
|
|
|
# anonymous sub für Abarbeitung Perl-Kommandos
|
|
$check = sub {
|
|
my ($cmd) = @_;
|
|
my $ret = AnalyzePerlCommand(undef, $cmd);
|
|
return $ret;
|
|
};
|
|
|
|
no warnings; ## no critic '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
|
|
|
|
for my $k (keys %{$specs->[$i]}) {
|
|
if ($k eq "icon") {
|
|
$ui = $specs->[$i]{$k};
|
|
$n--;
|
|
next;
|
|
}
|
|
|
|
for my $r (@{$allrds}) { # alle vorhandenen Readings evaluieren
|
|
if($r =~ m/$k$/x) {
|
|
(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) ) { ## no critic 'eval'
|
|
$ui = $specs->[$i]{icon};
|
|
$n--;
|
|
}
|
|
else {
|
|
$ui = "";
|
|
}
|
|
}
|
|
|
|
if($n == 0 && $ui) {
|
|
$default = $ui; # Defaultwert mit Select ersetzen wenn alle Bedingungen erfüllt
|
|
}
|
|
$i++;
|
|
}
|
|
}
|
|
elsif (ref($specs) eq "HASH") { # Wenn Schlüssel ein HASH enthält
|
|
my $n = keys %{$specs}; # Anzahl Elemente (Entscheidungskriterien) in Hash
|
|
|
|
for my $k (keys %{$specs}) {
|
|
if ($k eq "icon") {
|
|
$ui = $specs->{$k};
|
|
$n--;
|
|
next;
|
|
}
|
|
for my $r (@{$allrds}) { # alle vorhandenen Readings evaluieren
|
|
if($r =~ m/$k$/x) {
|
|
(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) ) { ## no critic 'eval'
|
|
$ui = $specs->{icon};
|
|
$n--;
|
|
}
|
|
else {
|
|
$ui = "";
|
|
}
|
|
}
|
|
|
|
if($n == 0 && $ui) {
|
|
$default = $ui; # Defaultwert mit Select ersetzen wenn alle Bedingungen erfüllt
|
|
}
|
|
}
|
|
else { # ref Wert der Eigenschaft ist nicht HASH oder ARRAY
|
|
if($specs =~ m/\{.*\}/xs) { # den Wert als Perl-Funktion ausführen wenn in {}
|
|
$specs =~ s/\$NAME/$name/xg; # Platzhalter $NAME, $BNR ersetzen
|
|
$specs =~ s/\$BNR/$bnr/xg;
|
|
$default = $check->($specs);
|
|
}
|
|
else { # einfache key-value Zuweisung
|
|
eval ($default = $specs); ## no critic 'eval'
|
|
}
|
|
}
|
|
}
|
|
|
|
if($default && $rdtype eq "image") {
|
|
$default = FW_makeImage($default); # Icon aus "string" errechnen wenn "image" als Rückgabe erwartet wird und $default gesetzt
|
|
}
|
|
|
|
use warnings;
|
|
|
|
return $default;
|
|
}
|
|
|
|
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>
|
|
|
|
This module is used to integrate Synology Calendar Server with FHEM.
|
|
The SSCal module is based on functions of Synology Calendar API. <br><br>
|
|
|
|
The connection to the calendar server is established via a session ID after successful login. Requirements/queries of the server
|
|
are stored internally in a queue and processed sequentially. If the calendar server is temporarily unavailable
|
|
the saved queries are retrieved as soon as the connection to the server is working again. <br><br>
|
|
|
|
Both appointment calendars (Events) and task lists (ToDo) can be processed. For these different calendar types
|
|
different device models can be defined, Model <b>Diary</b> for appointments and Model <b>Tasks</b> for
|
|
Task lists. <br><br>
|
|
|
|
If you want discuss about or like to support the development of this module, there is a thread in the FHEM forum:<br>
|
|
<a href="https://forum.fhem.de/index.php/topic,106963.0.html">57_SSCal - Modul für den Synology Kalender</a>.<br><br>
|
|
|
|
Further information about the module you can find in the (german) FHEM Wiki:<br>
|
|
<a href="https://wiki.fhem.de/wiki/SSCal_-_Integration_des_Synology_Calendar_Servers">SSCal - Integration des Synology Calendar Servers</a>.
|
|
<br><br><br>
|
|
|
|
|
|
<b>Preparation </b> <br><br>
|
|
|
|
<ul>
|
|
As basic requirement the <b>Synology Calendar Package</b> must be installed on your Synology Disc Station. <br>
|
|
In Synology DSM a user as member of the administrator group <b>must</b> be defined for access use. This user must also have the rights
|
|
to read and/or write the relevant calendars. The entitlement for the calendars are set directly in the
|
|
<a href="https://www.synology.com/en-global/knowledgebase/DSM/help/Calendar/calendar_desc">Synology calendar application</a>.
|
|
|
|
The login credentials are assigned later by the set <b>credentials</b> command to the defined device.
|
|
<br><br>
|
|
|
|
Furthermore some more Perl modules must be installed or available: <br><br>
|
|
|
|
<table>
|
|
<colgroup> <col width=35%> <col width=65%> </colgroup>
|
|
<tr><td>JSON </td><td> </td></tr>
|
|
<tr><td>Data::Dumper </td><td> </td></tr>
|
|
<tr><td>MIME::Base64 </td><td> </td></tr>
|
|
<tr><td>Time::HiRes </td><td> </td></tr>
|
|
<tr><td>Encode </td><td> </td></tr>
|
|
<tr><td>POSIX </td><td> </td></tr>
|
|
<tr><td>HttpUtils </td><td>(FHEM module) </td></tr>
|
|
<tr><td>Blocking </td><td>(FHEM module) </td></tr>
|
|
<tr><td>Meta </td><td>(FHEM module) </td></tr>
|
|
</table>
|
|
|
|
<br><br>
|
|
</ul>
|
|
|
|
<a name="SSCaldefine"></a>
|
|
<b>Definition</b>
|
|
<ul>
|
|
<br>
|
|
The creation of SSCal devices is differed between the definition of diaries and task lists.
|
|
<br><br>
|
|
|
|
The definition is done with: <br><br>
|
|
<ul>
|
|
<b><code>define <Name> SSCal <ServerAddr> [<Port>] [<Protocol>] [Tasks] </code></b> <br><br>
|
|
</ul>
|
|
|
|
The parameters are in detail:
|
|
<br>
|
|
<br>
|
|
|
|
<table>
|
|
<colgroup> <col width=10%> <col width=90%> </colgroup>
|
|
<tr><td><b>Name</b> </td><td>Name of the device in FHEM </td></tr>
|
|
<tr><td><b>ServerAddr</b> </td><td>IP address of Synology Disc Station. <b>Note:</b> If you use DNS name instead of IP address, don't forget to set the attribute dnsServer in global device ! </td></tr>
|
|
<tr><td><b>Port</b> </td><td>optional - Port of Synology Disc Station (default: 5000). </td></tr>
|
|
<tr><td><b>Protocol</b> </td><td>optional - Protocol used for communication with the calendar server, http or https (default: http). </td></tr>
|
|
<tr><td><b>Tasks</b> </td><td>optional - to define a task list device add "Tasks" to the definition </td></tr>
|
|
</table>
|
|
|
|
<br><br>
|
|
|
|
<b>Examples:</b>
|
|
<pre>
|
|
<code>define Appointments SSCal 192.168.2.10 </code>
|
|
<code>define Appointments SSCal 192.168.2.10 5001 https </code>
|
|
# creates a diary device on default port (5000/http) respectively https on port 5001
|
|
|
|
<code>define Tasklist SSCal ds.myds.org 5001 https Tasks </code>
|
|
# creates a task list device with protocol https on port 5001
|
|
</pre>
|
|
|
|
After definition of a device only the command <a href="#SSCalcredentials">credentials</a> is available.
|
|
First of all you have to set the credentials for communication with the Synology calendar server by using this command. <br><br>
|
|
|
|
If the login was successful, all for the user accessible calendars will be determined. The calendars to retrieve
|
|
are selectable by attribute <a href="#usedCalendars">usedCalendars</a>.
|
|
<br><br><br>
|
|
</ul>
|
|
|
|
<a name="SSCalset"></a>
|
|
<b>Set </b>
|
|
|
|
<ul>
|
|
<br>
|
|
The following set commands are valid for both device models Diary/Tasks or partly for one of these device models.
|
|
<br><br>
|
|
|
|
<ul>
|
|
<a name="SSCalcalUpdate"></a>
|
|
<li><b> calUpdate [<list of calendars>] </b> <br>
|
|
|
|
Fetch entries of the selected calendars (see attribute <a href="#usedCalendars">usedCalendars</a>).
|
|
Alternatively you can enter a list of calendars to fetch separated by comma. The calendar names may contain spaces.
|
|
<br><br>
|
|
|
|
<ul>
|
|
<b>Examples:</b> <br><br>
|
|
|
|
set Appointments calUpdate <br>
|
|
# fetch the entries of calendars specified in attribute usedCalendars <br><br>
|
|
|
|
set Appointments calUpdate Heikos Kalender,Abfall <br>
|
|
# fetch the entries of both calendars "Heikos Kalender" and "Abfall". <br><br>
|
|
</ul>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCalcleanCompleteTasks"></a>
|
|
<li><b> cleanCompleteTasks </b> (only model "Tasks") <br>
|
|
|
|
All completed tasks in the specified task lists (see attribute <a href="#usedCalendars">usedCalendars</a>) are deleted. <br>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCaldeleteEventId"></a>
|
|
<li><b> deleteEventId <Id> </b> <br>
|
|
|
|
The specified Event Id (see reading x_x_EventId) will be delted from calendar or tas list. <br>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCalcredentials"></a>
|
|
<li><b> credentials <User> <Passwort> </b> <br>
|
|
|
|
Store the credentials for calendar communication. <br>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCaleraseReadings"></a>
|
|
<li><b> eraseReadings </b> <br>
|
|
|
|
Delete all calendar readings. It doesn't effect the calendar entries itself ! <br>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCallistSendqueue"></a>
|
|
<li><b> listSendqueue </b> <br>
|
|
|
|
Shows all entries in the sendqueue. Normally the queue is filled only for a short time, but may contain entries
|
|
permanently in case of problems. Thereby the occured failures can be identified and assigned. <br>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCallogout"></a>
|
|
<li><b> logout </b> <br>
|
|
|
|
The user will be logged out and the session to the Synology Disc Station will be cleared. <br>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCalpurgeSendqueue"></a>
|
|
<li><b> purgeSendqueue </b> <br>
|
|
|
|
Deletes entries from the sendqueue. Several options are usable dependend from situation: <br><br>
|
|
<ul>
|
|
<table>
|
|
<colgroup> <col width=15%> <col width=85%> </colgroup>
|
|
<tr><td>-all- </td><td>deletes all entries from sendqueue </td></tr>
|
|
<tr><td>-permError- </td><td>deletes all entries which are suspended from further processing caused by a permanent error </td></tr>
|
|
<tr><td><Index> </td><td>deletes the specified entry from sendqueue </td></tr>
|
|
</table>
|
|
</ul>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCalrestartSendqueue"></a>
|
|
<li><b> restartSendqueue </b> <br>
|
|
|
|
The processing of entries in sendqueue will be new started manually. Because of the sendqueue will be restarted automatically by
|
|
every new retrieval it is normally not necessary to execute this command. <br>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
</ul>
|
|
|
|
<a name="SSCalget"></a>
|
|
<b>Get</b>
|
|
<ul>
|
|
<br>
|
|
|
|
<ul>
|
|
<a name="SSCalapiInfo"></a>
|
|
<li><b> apiInfo </b> <br>
|
|
|
|
Retrieves the API informations of the Synology calendar server and open a popup window with its data.
|
|
<br>
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCalcalAsHtml"></a>
|
|
<li><b> calAsHtml </b> <br>
|
|
|
|
Shows a popup with the time summary. In own perl routines and for integration in a weblink device this
|
|
overview can be used as follows: <br><br>
|
|
|
|
<ul>
|
|
{ FHEM::SSCal::calAsHtml ("<SSCal-Device>") }
|
|
</ul>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCalgetCalendars"></a>
|
|
<li><b> getCalendars </b> <br>
|
|
|
|
Requests the existing calendars from your Synology Disc Station and open a popup window with informations about each available calendar.
|
|
</li><br>
|
|
|
|
<br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCalstoredCredentials"></a>
|
|
<li><b> storedCredentials </b> <br>
|
|
|
|
Shows the stored User/Password combination.
|
|
</li><br>
|
|
|
|
<br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCalversionNotes"></a>
|
|
<li><b> versionNotes </b> <br>
|
|
|
|
Shows important informations and hints about the module.
|
|
</li><br>
|
|
|
|
<br>
|
|
</ul>
|
|
</ul>
|
|
|
|
<a name="SSCamattr"></a>
|
|
<b>Attribute</b>
|
|
<br><br>
|
|
<ul>
|
|
|
|
<ul>
|
|
<a name="asyncMode"></a>
|
|
<li><b>asyncMode</b> <br>
|
|
|
|
If set to "1", the data parsing will be executed within a background process and avoid possible blocking situations. <br>
|
|
(default: 0)
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="createATDevs"></a>
|
|
<li><b>createATDevs</b> <br>
|
|
|
|
If set to "1", FHEM commands and Perl routines to be executed are recognised automatically in a calendar entry by SSCal.
|
|
In this case SSCal defines, changes or deletes at-devices to execute these commands independently. <br>
|
|
A FHEM command to be executed has to be included into <b>{ }</b> in the field <b>Description</b> of Synology Calendar
|
|
application WebUI, Perl routines has to be included into double <b>{{ }}</b>. <br>
|
|
For further detailed information please read the Wiki (germnan) section:
|
|
<a href="https://wiki.fhem.de/wiki/-_Integration_des_Synology_Calendar_Servers#at-Devices_f.C3.BCr_Steuerungen_automatisch_erstellen_und_verwalten_lassen">at-Devices für Steuerungen automatisch erstellen und verwalten lassen</a>.
|
|
<br>
|
|
(default: 0)
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="cutOlderDays"></a>
|
|
<li><b>cutOlderDays</b> <br>
|
|
|
|
Entries in calendars are ignored if the due date is older than the number of specified days. <br>
|
|
(default: 5) <br><br>
|
|
|
|
<ul>
|
|
<b>Example:</b> <br><br>
|
|
|
|
attr <Name> cutOlderDays 30 <br>
|
|
</ul>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="cutLaterDays"></a>
|
|
<li><b>cutLaterDays</b> <br>
|
|
|
|
Entries in calendars are ignored if the due date is later than the number of specified days. <br>
|
|
(default: 5) <br><br>
|
|
|
|
<ul>
|
|
<b>Example:</b> <br><br>
|
|
|
|
attr <Name> cutLaterDays 90 <br>
|
|
</ul>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="filterCompleteTask"></a>
|
|
<li><b>filterCompleteTask </b> (only model "Tasks") <br>
|
|
|
|
Entries of the calendar are filtered dependend from their completion: <br><br>
|
|
|
|
<ul>
|
|
<table>
|
|
<colgroup> <col width=10%> <col width=90%> </colgroup>
|
|
<tr><td>1 </td><td>only completed tasks are shown </td></tr>
|
|
<tr><td>2 </td><td>only not completed tasks are shown </td></tr>
|
|
<tr><td>3 </td><td>completed and not completed tasks are shown (default) </td></tr>
|
|
</table>
|
|
</ul>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="filterDueTask"></a>
|
|
<li><b>filterDueTask </b> (only model "Tasks") <br>
|
|
|
|
Entries in taks lists with/without due date are filtered: <br><br>
|
|
|
|
<ul>
|
|
<table>
|
|
<colgroup> <col width=10%> <col width=90%> </colgroup>
|
|
<tr><td>1 </td><td>only tasks with due date are shown </td></tr>
|
|
<tr><td>2 </td><td>only tasks without due date are shown </td></tr>
|
|
<tr><td>3 </td><td>tasks with and without due date are shown (default) </td></tr>
|
|
</table>
|
|
</ul>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="interval"></a>
|
|
<li><b>interval <seconds></b> <br>
|
|
|
|
Interval in seconds to fetch calendar entries automatically. If "0" is specified, no calendar fetch is
|
|
executed. (default) <br><br>
|
|
|
|
<ul>
|
|
<b>Example:</b> <br><br>
|
|
|
|
Set the attribute as follows if the calendar entries should retrieved every hour: <br>
|
|
attr <Name> interval 3600 <br>
|
|
</ul>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="loginRetries"></a>
|
|
<li><b>loginRetries</b> <br>
|
|
|
|
Number of attempts for the initial user login. <br>
|
|
(default: 3)
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="showRepeatEvent"></a>
|
|
<li><b>showRepeatEvent</b> (only model "Diary") <br>
|
|
|
|
If "true", one-time events as well as recurrent events are fetched. Otherwise only one-time events are retrieved. <br>
|
|
(default: true)
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="showPassInLog"></a>
|
|
<li><b>showPassInLog</b> <br>
|
|
|
|
If "1", the password respectively the SID will be shown in the logfile. <br>
|
|
(default: 0)
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="tableColumnMap"></a>
|
|
<li><b>tableColumnMap </b> <br>
|
|
|
|
Determines how the link to a map is shown in the table column "Map": <br><br>
|
|
|
|
<ul>
|
|
<table>
|
|
<colgroup> <col width=10%> <col width=90%> </colgroup>
|
|
<tr><td><b>icon</b> </td><td>shows a user customisable symbol (default) </td></tr>
|
|
<tr><td><b>data</b> </td><td>shows the GPS data </td></tr>
|
|
<tr><td><b>text</b> </td><td>shows a text adjustable by the user </td></tr>
|
|
</table>
|
|
</ul>
|
|
|
|
<br>
|
|
To make further adjustments, there are some more possibilities to specify properties in the attribute tableSpecs.
|
|
For detailed informations about the possibilities to configure the overview table please consult the (german) Wiki
|
|
chapter
|
|
<a href="https://wiki.fhem.de/wiki/-_Integration_des_Synology_Calendar_Servers#Darstellung_der_.C3.9Cbersichtstabelle_in_Raum-_und_Detailansicht_beeinflussen">Darstellung der Übersichtstabelle in Raum- und Detailansicht beeinflussen</a>.
|
|
<br>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="tableInDetail"></a>
|
|
<li><b>tableInDetail</b> <br>
|
|
|
|
An overview diary or taks table will be displayed in detail view. <br>
|
|
(default: 1)
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="tableInRoom"></a>
|
|
<li><b>tableInRoom</b> <br>
|
|
|
|
An overview diary or taks table will be displayed in room view. <br>
|
|
(default: 1)
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="tableFields"></a>
|
|
<li><b>tableFields</b> <br>
|
|
|
|
Selection of the fields to be displayed in the overview table in room or detail view. <br>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="tableSpecs"></a>
|
|
<li><b>tableSpecs</b> <br>
|
|
|
|
By several key-value pair combinations the presentation of informations in the overview table can be adjusted.
|
|
The (german) Wiki chapter
|
|
<a href="https://wiki.fhem.de/wiki/-_Integration_des_Synology_Calendar_Servers#Darstellung_der_.C3.9Cbersichtstabelle_in_Raum-_und_Detailansicht_beeinflussen">Darstellung der Übersichtstabelle in Raum- und Detailansicht beeinflussen</a>
|
|
provides more detailed help for it.
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="timeout"></a>
|
|
<li><b>timeout <seconds></b> <br>
|
|
|
|
Timeout for calendar fetch in seconds. <br>
|
|
(default: 20)
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="usedCalendars"></a>
|
|
<li><b>usedCalendars</b> <br>
|
|
|
|
Selection of calendars to fetch from a popup window. The list of accessible calendars will initial be created during
|
|
FHEM startup. At all times it can also be done manually with command: <br><br>
|
|
|
|
<ul>
|
|
get <Name> getCalendars <br>
|
|
</ul>
|
|
|
|
<br>
|
|
|
|
As long as the accessible calendars are not successfully fetched, this attribute only contains simply the entry: <br><br>
|
|
|
|
<ul>
|
|
--wait for Calendar list-- <br>
|
|
</ul>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
</ul>
|
|
<br>
|
|
|
|
<a name="SSCalEvents"></a>
|
|
<b>Hints for event generation </b>
|
|
|
|
<ul>
|
|
<br>
|
|
Depending on the volume of the retrieved data, a large number of readings can be created.
|
|
To avoid too extensive event generation in FHEM, the attribute <b>event-on-update-reading</b> is preset after
|
|
definition of the calendar device to: <br><br>
|
|
|
|
<ul>
|
|
attr <name> event-on-update-reading.*Summary.*,state
|
|
</ul>
|
|
<br>
|
|
|
|
If events are to be created for all readings, event-on-update-reading must be set to .* and mustn't be deleted.
|
|
<br><br>
|
|
|
|
SSCal generates additional events for each event, which contains a start time, with each new read-in
|
|
of a calendar. These events provide the user with assistance in creating his own control logic in FHEM based on
|
|
calendar entries. <br><br>
|
|
|
|
The event <b>composite</b> contains the information fields: <br><br>
|
|
|
|
<ul>
|
|
<li> block number of the appointment </li>
|
|
<li>Event ID of the appointment </li>
|
|
<li>indicator for a serial appointment (0=no serial appointment or 1=serial appointment) </li>
|
|
<li>start time in ISO 8601 format </li>
|
|
<li>Status of the event </li>
|
|
<li>the text in Description (corresponds to the Description field in Synology Calendar WebUI) or the text in Summary
|
|
if Description is not set </li>
|
|
</ul>
|
|
<br>
|
|
|
|
The event <b>compositeBlockNumbers</b> contains the block numbers of all events of the calendar.
|
|
If there are no appointments, this event has only the value <b>none</b>.
|
|
|
|
</ul>
|
|
|
|
</ul>
|
|
|
|
=end html
|
|
=begin html_DE
|
|
|
|
<a name="SSCal"></a>
|
|
<h3>SSCal</h3>
|
|
<ul>
|
|
|
|
Mit diesem Modul erfolgt die Integration des Synology Calendar Servers in FHEM.
|
|
Das Modul SSCal basiert auf Funktionen der Synology Calendar API. <br><br>
|
|
|
|
Die Verbindung zum Kalenderserver erfolgt über eine Session ID nach erfolgreichem Login. Anforderungen/Abfragen des Servers
|
|
werden intern in einer Queue gespeichert und sequentiell abgearbeitet. Steht der Kalenderserver temporär nicht zur Verfügung,
|
|
werden die gespeicherten Abfragen nachgeholt sobald die Verbindung zum Server wieder funktioniert. <br><br>
|
|
|
|
Es können sowohl Terminkalender (Events) und Aufgabenlisten (ToDo) verarbeitet werden. Für diese verschiedenen Kalenderarten
|
|
können verschiedene Device-Models definiert werden, Model <b>Diary</b> für Terminkalender und Model <b>Tasks</b> für
|
|
Aufgabenlisten. <br><br>
|
|
|
|
Wenn sie über dieses Modul diskutieren oder zur Verbesserung des Moduls beitragen möchten, ist im FHEM-Forum ein Sammelplatz unter:<br>
|
|
<a href="https://forum.fhem.de/index.php/topic,106963.0.html">57_SSCal - Modul für den Synology Kalender</a>.<br><br>
|
|
|
|
Weitere Infomationen zum Modul sind im FHEM-Wiki zu finden:<br>
|
|
<a href="https://wiki.fhem.de/wiki/SSCal_-_Integration_des_Synology_Calendar_Servers">SSCal - Integration des Synology Calendar Servers</a>.
|
|
<br><br><br>
|
|
|
|
|
|
<b>Vorbereitung </b> <br><br>
|
|
|
|
<ul>
|
|
Als Grundvoraussetzung muss das <b>Synology Calendar Package</b> auf der Diskstation installiert sein. <br>
|
|
Im Synology DSM wird ein User benutzt, der Mitglied der Administrator-Group sein <b>muß</b> und zusätzlich die benötigte Berechtigung
|
|
zum Lesen und/oder Schreiben der relevanten Kalender hat. Die Kalenderberechtigungen werden direkt in der
|
|
<a href="https://www.synology.com/de-de/knowledgebase/DSM/help/Calendar/calendar_desc">Synology Kalenderapplikation</a> eingestellt.
|
|
|
|
Die Zugangsdaten werden später über ein Set <b>credentials</b> Kommando dem angelegten Device zugewiesen.
|
|
<br><br>
|
|
|
|
Weiterhin müssen diverse Perl-Module installiert sein: <br><br>
|
|
|
|
<table>
|
|
<colgroup> <col width=35%> <col width=65%> </colgroup>
|
|
<tr><td>JSON </td><td> </td></tr>
|
|
<tr><td>Data::Dumper </td><td> </td></tr>
|
|
<tr><td>MIME::Base64 </td><td> </td></tr>
|
|
<tr><td>Time::HiRes </td><td> </td></tr>
|
|
<tr><td>Encode </td><td> </td></tr>
|
|
<tr><td>POSIX </td><td> </td></tr>
|
|
<tr><td>HttpUtils </td><td>(FHEM-Modul) </td></tr>
|
|
<tr><td>Blocking </td><td>(FHEM-Modul) </td></tr>
|
|
<tr><td>Meta </td><td>(FHEM-Modul) </td></tr>
|
|
</table>
|
|
|
|
<br><br>
|
|
</ul>
|
|
|
|
<a name="SSCaldefine"></a>
|
|
<b>Definition</b>
|
|
<ul>
|
|
<br>
|
|
Bei der Definition wird zwischen einem Kalenderdevice für Termine (Events) und Aufgaben (Tasks) unterschieden.
|
|
<br><br>
|
|
|
|
Die Definition erfolgt mit: <br><br>
|
|
<ul>
|
|
<b><code>define <Name> SSCal <ServerAddr> [<Port>] [<Protocol>] [Tasks] </code></b> <br><br>
|
|
</ul>
|
|
|
|
Die Parameter beschreiben im Einzelnen:
|
|
<br>
|
|
<br>
|
|
|
|
<table>
|
|
<colgroup> <col width=10%> <col width=90%> </colgroup>
|
|
<tr><td><b>Name</b> </td><td>der Name des neuen Kalenderdevices in FHEM </td></tr>
|
|
<tr><td><b>ServerAddr</b> </td><td>die IP-Addresse der Synology DS. <b>Hinweis:</b> Wird der DNS-Name statt IP-Adresse verwendet, sollte das Attribut dnsServer im global Device gesetzt werden ! </td></tr>
|
|
<tr><td><b>Port</b> </td><td>optional - Port der Synology DS (default: 5000). </td></tr>
|
|
<tr><td><b>Protocol</b> </td><td>optional - Protokoll zur Kommunikation mit dem Kalender-Server, http oder https (default: http). </td></tr>
|
|
<tr><td><b>Tasks</b> </td><td>optional - zur Definition einer Aufgabenliste wird "Tasks" hinzugefügt </td></tr>
|
|
</table>
|
|
|
|
<br><br>
|
|
|
|
<b>Beispiele:</b>
|
|
<pre>
|
|
<code>define Appointments SSCal 192.168.2.10 </code>
|
|
<code>define Appointments SSCal 192.168.2.10 5001 https </code>
|
|
# erstellt Terminkalenderdevice mit Standardport (5000/http) bzw. https auf Port 5001
|
|
|
|
<code>define Tasklist SSCal ds.myds.org 5001 https Tasks </code>
|
|
# erstellt Aufgabenlistendevice mit https auf Port 5001
|
|
</pre>
|
|
|
|
Nach der Definition eines Devices steht nur der set-Befehl <a href="#SSCalcredentials">credentials</a> zur Verfügung.
|
|
Mit diesem Befehl werden zunächst die Zugangsparameter dem Device bekannt gemacht. <br><br>
|
|
|
|
War der Login erfolgreich, werden alle dem User zugänglichen Kalender ermittelt und im
|
|
Attribut <a href="#usedCalendars">usedCalendars</a> zur Auswahl bereitgestellt.
|
|
<br><br><br>
|
|
</ul>
|
|
|
|
<a name="SSCalset"></a>
|
|
<b>Set </b>
|
|
|
|
<ul>
|
|
<br>
|
|
Die aufgeführten set-Kommandos sind sowohl für die Devicemodels Diary/Tasks oder teilweise nur für einen dieser Devicemodels gültig.
|
|
<br><br>
|
|
|
|
<ul>
|
|
<a name="SSCalcalUpdate"></a>
|
|
<li><b> calUpdate [<Kalenderliste>] </b> <br>
|
|
|
|
Ruft die Einträge der selektierten Kalender (siehe Attribut <a href="#usedCalendars">usedCalendars</a>) ab.
|
|
Alternativ kann eine Komma getrennte Liste der abzurufenden Kalender dem Befehl übergeben werden. Die Kalendernamen können
|
|
Leerzeichen enthalten.
|
|
<br><br>
|
|
|
|
<ul>
|
|
<b>Beispiel:</b> <br><br>
|
|
|
|
set Appointments calUpdate <br>
|
|
# ruft die Einträge der im Attribut usedCalendars spezifizierten Kalender ab <br><br>
|
|
|
|
set Appointments calUpdate Heikos Kalender,Abfall <br>
|
|
# ruft die Einträge der Kalender "Heikos Kalender" und "Abfall" ab. <br><br>
|
|
</ul>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCalcleanCompleteTasks"></a>
|
|
<li><b> cleanCompleteTasks </b> (nur Model "Tasks") <br>
|
|
|
|
In den selektierten Aufgabenlisten (siehe Attribut <a href="#usedCalendars">usedCalendars</a>) werden alle
|
|
abgeschlossenen Aufgaben gelöscht. <br>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCaldeleteEventId"></a>
|
|
<li><b> deleteEventId <Id> </b> <br>
|
|
|
|
Die angegebene Event Id (siehe Reading x_x_EventId) wird aus dem Kalender bzw. der Aufgabenliste gelöscht. <br>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCalcredentials"></a>
|
|
<li><b> credentials <User> <Passwort> </b> <br>
|
|
|
|
Speichert die Zugangsdaten. <br>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCaleraseReadings"></a>
|
|
<li><b> eraseReadings </b> <br>
|
|
|
|
Löscht alle Kalenderreadings. <br>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCallistSendqueue"></a>
|
|
<li><b> listSendqueue </b> <br>
|
|
|
|
Zeigt alle Einträge in der Sendequeue. Die Queue ist normalerweise nur kurz gefüllt, kann aber im Problemfall
|
|
dauerhaft Einträge enthalten. Dadurch kann ein bei einer Abrufaufgabe aufgetretener Fehler ermittelt und zugeordnet
|
|
werden. <br>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCallogout"></a>
|
|
<li><b> logout </b> <br>
|
|
|
|
Der User wird ausgeloggt und die Session mit der Synology DS beendet. <br>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCalpurgeSendqueue"></a>
|
|
<li><b> purgeSendqueue </b> <br>
|
|
|
|
Löscht Einträge in der Sendequeue. Es stehen verschiedene Optionen je nach Situation zur Verfügung: <br><br>
|
|
<ul>
|
|
<table>
|
|
<colgroup> <col width=15%> <col width=85%> </colgroup>
|
|
<tr><td>-all- </td><td>löscht alle in der Sendequeue vorhandenen Einträge </td></tr>
|
|
<tr><td>-permError- </td><td>löscht alle Einträge, die durch einen permanenten Fehler von der weiteren Verarbeitung ausgeschlossen sind </td></tr>
|
|
<tr><td><Index> </td><td>löscht einen eindeutigen Eintrag der Sendequeue </td></tr>
|
|
</table>
|
|
</ul>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCalrestartSendqueue"></a>
|
|
<li><b> restartSendqueue </b> <br>
|
|
|
|
Die Abarbeitung der Einträge in der Sendequeue wird manuell neu angestoßen. Normalerweise nicht nötig, da die Sendequeue bei der
|
|
Initialisierung jedes neuen Abrufs impliziz neu gestartet wird. <br>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
</ul>
|
|
|
|
<a name="SSCalget"></a>
|
|
<b>Get</b>
|
|
<ul>
|
|
<br>
|
|
|
|
<ul>
|
|
<a name="SSCalapiInfo"></a>
|
|
<li><b> apiInfo </b> <br>
|
|
|
|
Ruft die API Informationen des Synology Calendar Servers ab und öffnet ein Popup mit diesen Informationen.
|
|
<br>
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCalcalAsHtml"></a>
|
|
<li><b> calAsHtml </b> <br>
|
|
|
|
Zeigt ein Popup mit einer Terminübersicht. In eigenen perl-Routinen und für die Einbindung in weblink kann
|
|
diese Übersicht aufgerufen werden mit: <br><br>
|
|
|
|
<ul>
|
|
{ FHEM::SSCal::calAsHtml ("<SSCal-Device>") }
|
|
</ul>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCalgetCalendars"></a>
|
|
<li><b> getCalendars </b> <br>
|
|
|
|
Ruft die auf der Synology vorhandenen Kalender ab und öffnet ein Popup mit Informationen über die jeweiligen Kalender.
|
|
</li><br>
|
|
|
|
<br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCalstoredCredentials"></a>
|
|
<li><b> storedCredentials </b> <br>
|
|
|
|
Zeigt die gespeicherten User/Passwort Kombination.
|
|
</li><br>
|
|
|
|
<br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="SSCalversionNotes"></a>
|
|
<li><b> versionNotes </b> <br>
|
|
|
|
Zeigt Informationen und Hilfen zum Modul.
|
|
</li><br>
|
|
|
|
<br>
|
|
</ul>
|
|
</ul>
|
|
|
|
<a name="SSCamattr"></a>
|
|
<b>Attribute</b>
|
|
<br><br>
|
|
<ul>
|
|
|
|
<ul>
|
|
<a name="asyncMode"></a>
|
|
<li><b>asyncMode</b> <br>
|
|
|
|
Wenn "1" wird das Datenparsing in einen Hintergrundprozess ausgelagert und vermeidet Blockierungssituationen. <br>
|
|
(default: 0)
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="createATDevs"></a>
|
|
<li><b>createATDevs</b> <br>
|
|
|
|
Wenn "1" werden bei der Erkennung von FHEM-Kommandos bzw. auszuführenden Perl-Routinen im Kalendereintrag durch SSCal
|
|
automatisiert at-Devices zur termingerechten Ausführung dieser Kommandos erstellt, geändert und gelöscht. <br>
|
|
Auszuführende FHEM-Kommandos werden in <b>{ }</b> eingeschlossen im Feld <b>Beschreibung</b> im Synology Kalender WebUI
|
|
hinterlegt, Perl Routinen werden in doppelte <b>{{ }}</b> eingeschlossen. <br>
|
|
Lesen sie bitte dazu die detailliierte Beschreibung im Wiki Abschnitt
|
|
<a href="https://wiki.fhem.de/wiki/-_Integration_des_Synology_Calendar_Servers#at-Devices_f.C3.BCr_Steuerungen_automatisch_erstellen_und_verwalten_lassen">at-Devices für Steuerungen automatisch erstellen und verwalten lassen</a>.
|
|
<br>
|
|
(default: 0)
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="cutOlderDays"></a>
|
|
<li><b>cutOlderDays</b> <br>
|
|
|
|
Terminkalendereinträge und Aufgabenkalendereinträge mit Fälligkeitstermin älter als die angegeben Tage werden von der
|
|
Verarbeitung ausgeschlossen. <br>
|
|
(default: 5) <br><br>
|
|
|
|
<ul>
|
|
<b>Beispiel:</b> <br><br>
|
|
|
|
attr <Name> cutOlderDays 30 <br>
|
|
</ul>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="cutLaterDays"></a>
|
|
<li><b>cutLaterDays</b> <br>
|
|
|
|
Terminkalendereinträge und Aufgabenkalendereinträge mit Fälligkeitstermin später als die angegeben Tage werden von der
|
|
Verarbeitung ausgeschlossen. <br>
|
|
(default: 5) <br><br>
|
|
|
|
<ul>
|
|
<b>Beispiel:</b> <br><br>
|
|
|
|
attr <Name> cutLaterDays 90 <br>
|
|
</ul>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="filterCompleteTask"></a>
|
|
<li><b>filterCompleteTask </b> (nur Model "Tasks") <br>
|
|
|
|
Es werden Einträge in Aufgabenkalendern entsprechend der Fertigstellung gefiltert: <br><br>
|
|
|
|
<ul>
|
|
<table>
|
|
<colgroup> <col width=10%> <col width=90%> </colgroup>
|
|
<tr><td>1 </td><td>nur fertig gestellte Aufgaben werden angezeigt </td></tr>
|
|
<tr><td>2 </td><td>nur nicht fertige Aufgaben werden angezeigt </td></tr>
|
|
<tr><td>3 </td><td>es werden fertige und nicht fertige Aufgaben angezeigt (default) </td></tr>
|
|
</table>
|
|
</ul>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="filterDueTask"></a>
|
|
<li><b>filterDueTask </b> (nur Model "Tasks") <br>
|
|
|
|
Es werden Einträge in Aufgabenkalendern mit/ohne Fälligkeit gefiltert: <br><br>
|
|
|
|
<ul>
|
|
<table>
|
|
<colgroup> <col width=10%> <col width=90%> </colgroup>
|
|
<tr><td>1 </td><td>nur Einträge mit Fälligkeitstermin werden angezeigt </td></tr>
|
|
<tr><td>2 </td><td>nur Einträge ohne Fälligkeitstermin werden angezeigt </td></tr>
|
|
<tr><td>3 </td><td>es werden Einträge mit und ohne Fälligkeitstermin angezeigt (default) </td></tr>
|
|
</table>
|
|
</ul>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="interval"></a>
|
|
<li><b>interval <Sekunden></b> <br>
|
|
|
|
Automatisches Abrufintervall der Kalendereintträge in Sekunden. Ist "0" agegeben, wird kein automatischer Datenabruf
|
|
ausgeführt. (default) <br>
|
|
Sollen z.B. jede Stunde die Einträge der gewählten Kalender abgerufen werden, wird das Attribut wie
|
|
folgt gesetzt: <br><br>
|
|
|
|
<ul>
|
|
attr <Name> interval 3600 <br>
|
|
</ul>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="loginRetries"></a>
|
|
<li><b>loginRetries</b> <br>
|
|
|
|
Anzahl der Versuche für das inititiale User login. <br>
|
|
(default: 3)
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="showRepeatEvent"></a>
|
|
<li><b>showRepeatEvent</b> (nur Model "Diary") <br>
|
|
|
|
Wenn "true" werden neben einmaligen Terminen ebenfalls wiederkehrende Termine ausgewertet. <br>
|
|
(default: true)
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="showPassInLog"></a>
|
|
<li><b>showPassInLog</b> <br>
|
|
|
|
Wenn "1" wird das Passwort bzw. die SID im Log angezeigt. <br>
|
|
(default: 0)
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="tableColumnMap"></a>
|
|
<li><b>tableColumnMap </b> <br>
|
|
|
|
Legt fest, wie der Link zur Karte in der Tabellspalte "Map" bzw. "Karte" gestaltet wird: <br><br>
|
|
|
|
<ul>
|
|
<table>
|
|
<colgroup> <col width=10%> <col width=90%> </colgroup>
|
|
<tr><td><b>icon</b> </td><td>es wird ein durch den User anpassbares Symbol angezeigt (default) </td></tr>
|
|
<tr><td><b>data</b> </td><td>es werden die GPS-Daten angezeigt </td></tr>
|
|
<tr><td><b>text</b> </td><td>es wird ein durch den Nutzer einstellbarer Text verwendet </td></tr>
|
|
</table>
|
|
</ul>
|
|
|
|
<br>
|
|
Der Nutzer kann weitere Anpassungen des verwendeten Icons oder Textes in den Eigenschaften des Attributs tableSpecs
|
|
vornehmen. Für detailliierte Informationen dazu siehe Wiki-Kapitel
|
|
<a href="https://wiki.fhem.de/wiki/-_Integration_des_Synology_Calendar_Servers#Darstellung_der_.C3.9Cbersichtstabelle_in_Raum-_und_Detailansicht_beeinflussen">Darstellung der Übersichtstabelle in Raum- und Detailansicht beeinflussen</a>.
|
|
<br>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="tableInDetail"></a>
|
|
<li><b>tableInDetail</b> <br>
|
|
|
|
Eine Termin/Aufgabenübersicht wird in der Detailansicht erstellt bzw. ausgeschaltet. <br>
|
|
(default: 1)
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="tableInRoom"></a>
|
|
<li><b>tableInRoom</b> <br>
|
|
|
|
Eine Termin/Aufgabenübersicht wird in der Raumansicht erstellt bzw. ausgeschaltet. <br>
|
|
(default: 1)
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="tableFields"></a>
|
|
<li><b>tableFields</b> <br>
|
|
|
|
Auswahl der in der Termin/Aufgabenübersicht (Raum- bzw. Detailansicht) anzuzeigenden Felder über eine Drop-Down
|
|
Liste. <br>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="tableSpecs"></a>
|
|
<li><b>tableSpecs</b> <br>
|
|
|
|
Über verschiedene Schlüssel-Wertpaar Kombinationen kann die Darstellung der Informationen in der Übersichtstabelle
|
|
angepasst werden. Das Wiki-Kapitel
|
|
<a href="https://wiki.fhem.de/wiki/SSCal_-_Integration_des_Synology_Calendar_Servers#Darstellung_der_.C3.9Cbersichtstabelle_in_Raum-_und_Detailansicht_beeinflussen">Darstellung der Übersichtstabelle in Raum- und Detailansicht beeinflussen</a>
|
|
liefert detailiierte Informationen dazu.
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="timeout"></a>
|
|
<li><b>timeout <Sekunden></b> <br>
|
|
|
|
Timeout für den Datenabruf in Sekunden. <br>
|
|
(default: 20)
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
<ul>
|
|
<a name="usedCalendars"></a>
|
|
<li><b>usedCalendars</b> <br>
|
|
|
|
Auswahl der abzurufenden Kalender über ein Popup. Die Liste der Kalender wird beim Start des Moduls initial gefüllt,
|
|
kann danach aber ebenfalls durch den Befehl: <br><br>
|
|
|
|
<ul>
|
|
get <Name> getCalendars <br>
|
|
</ul>
|
|
|
|
<br>
|
|
manuell ausgeführt werden.
|
|
Wurde noch kein erfolgreicher Kalenderabruf ausgeführt, enthält dieses Attribut lediglich den Eintrag: <br><br>
|
|
|
|
<ul>
|
|
--wait for Calendar list-- <br>
|
|
</ul>
|
|
|
|
</li><br>
|
|
</ul>
|
|
|
|
</ul>
|
|
<br>
|
|
|
|
<a name="SSCalEvents"></a>
|
|
<b>Hinweise zur Eventgenerierung </b>
|
|
|
|
<ul>
|
|
<br>
|
|
Je nach Umfang der abgerufenen Daten können sehr viele Readings erstellt werden. Um eine zu umfangreiche Eventgenerierung
|
|
in FHEM zu verhindern, ist nach der Definition des Kalenderdevices das Attribut <b>event-on-update-reading</b>
|
|
voreingestellt auf: <br><br>
|
|
|
|
<ul>
|
|
attr <Name> event-on-update-reading .*Summary.*,state
|
|
</ul>
|
|
<br>
|
|
|
|
Sollen Events für alle Readings erstellt werden, muss event-on-update-reading auf .* eingestellt und nicht gelöscht
|
|
werden. <br><br>
|
|
|
|
SSCal generiert für jedes Ereignis, welches einen Begin-Zeitpunkt enthält, zusätzliche Events bei jedem erneuten Einlesen
|
|
eines Kalenders. Diese Events bieten dem Anwender Hilfe zur Erstellung eigener Steuerungslogiken in FHEM auf Grundlage
|
|
von Kalendereinträgen. <br><br>
|
|
|
|
Der Event <b>composite</b> enthält die Informationsfelder: <br><br>
|
|
|
|
<ul>
|
|
<li>Blocknummer des Termins </li>
|
|
<li>Event-ID des Termins </li>
|
|
<li>Kennzeichen für ein Serientermin (0=kein Serientermin oder 1=Serientermin) </li>
|
|
<li>Startzeitpunkt im ISO 8601 Format </li>
|
|
<li>Status des Events </li>
|
|
<li>den Text in Description (entspricht dem Feld Beschreibung im Synology Kalender WebUI) bzw. den Text in Summary
|
|
falls Description nicht gesetzt ist </li>
|
|
</ul>
|
|
<br>
|
|
|
|
Der Event <b>compositeBlockNumbers</b> enthält die Blocknummern aller Termine des Kalenders. Sind keine Termine vorhanden, enthält
|
|
dieser Event nur den Wert <b>none</b>.
|
|
|
|
</ul>
|
|
|
|
</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",
|
|
"release_status": "stable",
|
|
"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,
|
|
"POSIX": 0,
|
|
"JSON": 4.020,
|
|
"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
|