From 3ab7103da5d9baeb9f5f4779b39af5e391e9b3cd Mon Sep 17 00:00:00 2001 From: DS_Starter Date: Sat, 29 Aug 2020 12:44:39 +0000 Subject: [PATCH] SynoModules: update Synology libraries git-svn-id: https://svn.fhem.de/fhem/trunk@22686 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/MAINTAINER.txt | 5 +- fhem/lib/FHEM/SynoModules/ErrCodes.pm | 243 +++++++++++++++++++ fhem/lib/FHEM/SynoModules/SMUtils.pm | 329 ++++++++++++++++++++++++-- 3 files changed, 551 insertions(+), 26 deletions(-) create mode 100644 fhem/lib/FHEM/SynoModules/ErrCodes.pm diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index 944d046bc..55b01fa37 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -601,8 +601,9 @@ FHEM/lib/SWAP/* justme1968 Sonstige Systeme FHEM/lib/UPnP/* Reinerlein Multimedia lib/FHEM/Core/Timer/Helper.pm sidey79 FHEM Development -lib/FHEM/SynoModules/API.pm DS_Starter Sonstiges -lib/FHEM/SynoModules/SMUtils.pm DS_Starter Sonstiges +lib/FHEM/SynoModules/API.pm DS_Starter Sonstiges +lib/FHEM/SynoModules/SMUtils.pm DS_Starter Sonstiges +lib/FHEM/SynoModules/ErrCodes.pm DS_Starter Sonstiges contrib/sacha_gloor/* rudolfkoenig Sonstiges contrib/70_ONKYO_AVR_PULL.pm loredo (deprecated) diff --git a/fhem/lib/FHEM/SynoModules/ErrCodes.pm b/fhem/lib/FHEM/SynoModules/ErrCodes.pm new file mode 100644 index 000000000..e9f4d1908 --- /dev/null +++ b/fhem/lib/FHEM/SynoModules/ErrCodes.pm @@ -0,0 +1,243 @@ +######################################################################################################################## +# $Id$ +######################################################################################################################### +# ErrCodes.pm +# +# (c) 2020 by Heiko Maaz +# e-mail: Heiko dot Maaz at t-online dot de +# +# This Module provides Synology API Error Codes. +# +# 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 . +# +######################################################################################################################### + +package FHEM::SynoModules::ErrCodes; + +use strict; +use warnings; +use utf8; +use Carp qw(croak carp); + +use version; our $VERSION = version->declare('1.0.0'); + +use Exporter ('import'); +our @EXPORT_OK = qw(expErrorsAuth expErrors); +our %EXPORT_TAGS = (all => [@EXPORT_OK]); + +my %hterr = ( # Hash der TYPE Error Code Spezifikationen + SSCam => {fnerrauth => "_errauthsscam", fnerr => "_errsscam" }, + SSCal => {fnerrauth => "_errauthsscal", fnerr => "_errsscal" }, +); + +# Standard Rückgabewert wenn keine Message zum Error Code gefunden wurde +my $nofound = qq{Message not found for error code:}; + +############################################################################## +# Error Code Hashes +############################################################################## +## SSCam ## +my %errauthsscam = ( # Authentification Error Codes der Surveillance Station API + 100 => "Unknown error", + 101 => "The account parameter is not specified", + 102 => "API does not exist", + 400 => "Invalid user or password", + 401 => "Guest or disabled account", + 402 => "Permission denied - DSM-Session: make sure user is member of Admin-group, SVS-Session: make sure SVS package is started, make sure FHEM-Server IP won't be blocked in DSM automated blocking list", + 403 => "One time password not specified", + 404 => "One time password authenticate failed", + 405 => "method not allowd - maybe the password is too long", + 406 => "OTP code enforced", + 407 => "Max Tries (if auto blocking is set to true) - make sure FHEM-Server IP won't be blocked in DSM automated blocking list", + 408 => "Password Expired Can not Change", + 409 => "Password Expired", + 410 => "Password must change (when first time use or after reset password by admin)", + 411 => "Account Locked (when account max try exceed)", +); + +my %errsscam = ( # Standard Error Codes der Surveillance Station API + 100 => "Unknown error", + 101 => "Invalid parameters", + 102 => "API does not exist", + 103 => "Method does not exist", + 104 => "This API version is not supported", + 105 => "Insufficient user privilege", + 106 => "Connection time out", + 107 => "Multiple login detected", + 117 => "need manager rights in SurveillanceStation for operation", + 400 => "Execution failed", + 401 => "Parameter invalid", + 402 => "Camera disabled", + 403 => "Insufficient license", + 404 => "Codec activation failed", + 405 => "CMS server connection failed", + 407 => "CMS closed", + 410 => "Service is not enabled", + 412 => "Need to add license", + 413 => "Reach the maximum of platform", + 414 => "Some events not exist", + 415 => "message connect failed", + 417 => "Test Connection Error", + 418 => "Object is not exist", + 419 => "Visualstation name repetition", + 439 => "Too many items selected", + 502 => "Camera disconnected", + 600 => "Presetname and PresetID not found in Hash", +); + +## SSCal ## +my %errauthsscal = ( # Authentification Error Codes der Calendar API + 400 => "No such account or the password is incorrect", + 401 => "Account disabled", + 402 => "Permission denied", + 403 => "2-step verification code required", + 404 => "Failed to authenticate 2-step verification code", +); + +my %errsscal = ( # Standard Error Codes der Calendar API + 100 => "Unknown error", + 101 => "No parameter of API, method or version", + 102 => "The requested API does not exist - may be the Synology Calendar package is stopped", + 103 => "The requested method does not exist", + 104 => "The requested version does not support the functionality", + 105 => "The logged in session does not have permission", + 106 => "Session timeout", + 107 => "Session interrupted by duplicate login", + 114 => "Missing required parameters", + 117 => "Unknown internal error", + 119 => "session id not valid", + 120 => "Invalid parameter", + 160 => "Insufficient application privilege", + 400 => "Invalid parameter of file operation", + 401 => "Unknown error of file operation", + 402 => "System is too busy", + 403 => "The user does not have permission to execute this operation", + 404 => "The group does not have permission to execute this operation", + 405 => "The user/group does not have permission to execute this operation", + 406 => "Cannot obtain user/group information from the account server", + 407 => "Operation not permitted", + 408 => "No such file or directory", + 409 => "File system not supported", + 410 => "Failed to connect internet-based file system (ex: CIFS)", + 411 => "Read-only file system", + 412 => "Filename too long in the non-encrypted file system", + 413 => "Filename too long in the encrypted file system", + 414 => "File already exists", + 415 => "Disk quota exceeded", + 416 => "No space left on device", + 417 => "Input/output error", + 418 => "Illegal name or path", + 419 => "Illegal file name", + 420 => "Illegal file name on FAT file system", + 421 => "Device or resource busy", + 599 => "No such task of the file operation", + 800 => "malformed or unsupported URL", + 805 => "empty API data received - may be the Synology cal Server package is stopped", + 806 => "couldn't get Synology cal API information", + 810 => "The credentials couldn't be retrieved", + 900 => "malformed JSON string received from Synology Calendar Server", + 910 => "Wrong timestamp definition. Check attributes \"cutOlderDays\", \"cutLaterDays\". ", +); + +############################################################################## +# Auflösung Errorcodes bei Login / Logout +############################################################################## +sub expErrorsAuth { + my $hash = shift // carp "got no hash value !" && return; + my $errorcode = shift // carp "got no error code to analyse" && return; + my $type = $hash->{TYPE}; + + no strict "refs"; ## no critic 'NoStrict' + if($hterr{$type} && defined &{$hterr{$type}{fnerrauth}}) { + my $error = &{$hterr{$type}{fnerrauth}} ($errorcode); + return $error; + } + use strict "refs"; + + carp qq{No resolution function of authentication errors for module type "$type" defined}; + +return q{}; +} + +############################################################################## +# Auflösung Standard Errorcodes +############################################################################## +sub expErrors { + my $hash = shift // carp "got no hash value !" && return; + my $errorcode = shift // carp "got no error code to analyse" && return; + my $type = $hash->{TYPE}; + + no strict "refs"; ## no critic 'NoStrict' + if($hterr{$type} && defined &{$hterr{$type}{fnerr}}) { + my $error = &{$hterr{$type}{fnerr}} ($errorcode); + return $error; + } + use strict "refs"; + + carp qq{No resolution function of authentication errors for module type "$type" defined}; + +return q{}; +} + +############################################################################## +# Liefert Fehlertext für einen +# Authentification Error Code der Surveillance Station API +############################################################################## +sub _errauthsscam { ## no critic "not used" + my $errorcode = shift; + + my $error = $errauthsscam{"$errorcode"} // $nofound." ".$errorcode; + +return $error; +} + +############################################################################## +# Liefert Fehlertext für einen +# Standard Error Code der Surveillance Station API +############################################################################## +sub _errsscam { ## no critic "not used" + my $errorcode = shift; + + my $error = $errsscam{"$errorcode"} // $nofound." ".$errorcode; + +return $error; +} + +############################################################################## +# Liefert Fehlertext für einen +# Authentification Error Code der Calendar API +############################################################################## +sub _errauthsscal { ## no critic "not used" + my $errorcode = shift; + + my $error = $errauthsscal{"$errorcode"} // $nofound." ".$errorcode; + +return $error; +} + +############################################################################## +# Liefert Fehlertext für einen +# Standard Error Code der Calendar API +############################################################################## +sub _errsscal { ## no critic "not used" + my $errorcode = shift; + + my $error = $errsscal{"$errorcode"} // $nofound." ".$errorcode; + +return $error; +} + +1; \ No newline at end of file diff --git a/fhem/lib/FHEM/SynoModules/SMUtils.pm b/fhem/lib/FHEM/SynoModules/SMUtils.pm index 52d731f55..0196decfb 100644 --- a/fhem/lib/FHEM/SynoModules/SMUtils.pm +++ b/fhem/lib/FHEM/SynoModules/SMUtils.pm @@ -32,24 +32,31 @@ use warnings; use utf8; use MIME::Base64; eval "use JSON;1;" or my $nojsonmod = 1; ## no critic 'eval' +use Data::Dumper; -# use lib qw(/opt/fhem/FHEM); # für Syntaxcheck mit: perl -c /opt/fhem/lib/FHEM/SynoModules/SMUtils.pm +# use lib qw(/opt/fhem/FHEM /opt/fhem/lib); # für Syntaxcheck mit: perl -c /opt/fhem/lib/FHEM/SynoModules/SMUtils.pm + +use FHEM::SynoModules::ErrCodes qw(:all); # Error Code Modul use GPUtils qw( GP_Import GP_Export ); use Carp qw(croak carp); -use version; our $VERSION = version->declare('1.2.0'); +use version; our $VERSION = version->declare('1.3.0'); use Exporter ('import'); -our @EXPORT_OK = qw( - getClHash - trim - sortVersion - setVersionInfo - jboolmap - setCredentials - getCredentials - evaljson - ); +our @EXPORT_OK = qw( + getClHash + trim + sortVersion + setVersionInfo + jboolmap + setCredentials + getCredentials + evaljson + login + logout + setActiveToken + delActiveToken + ); our %EXPORT_TAGS = (all => [@EXPORT_OK]); @@ -62,12 +69,15 @@ BEGIN { Log3 defs modules + CancelDelayedShutdown devspec2array setKeyValue getKeyValue + readingsSingleUpdate readingsBeginUpdate readingsBulkUpdate readingsEndUpdate + HttpUtils_NonblockingGet ) ); }; @@ -77,7 +87,7 @@ BEGIN { # Identifikation ob über FHEMWEB ausgelöst oder nicht -> erstellen $hash->CL ############################################################################### sub getClHash { - my $hash = shift // carp "got no hash value !" && return; + my $hash = shift // carp "got no hash value" && return; my $nobgd = shift; my $name = $hash->{NAME}; my $ret; @@ -159,8 +169,8 @@ return @sorted; # Die Verwendung von Meta.pm und Packages wird berücksichtigt ############################################################################################# sub setVersionInfo { - my $hash = shift // carp "got no hash value !" && return; - my $notes = shift // carp "got no vNotesIntern value !" && return; + my $hash = shift // carp "got no hash value" && return; + my $notes = shift // carp "got no vNotesIntern value" && return; my $name = $hash->{NAME}; my $v = (sortVersion("desc",keys %{$notes}))[0]; @@ -196,7 +206,7 @@ return; # JSON Boolean Test und Mapping ############################################################################### sub jboolmap { - my $bool = shift // carp "got no value to check if bool !" && return; + my $bool = shift // carp "got no value to check if bool" && return; my $is_boolean = JSON::is_bool($bool); @@ -213,10 +223,10 @@ return $bool; # $ao = "SMTPcredentials" -> Credentials für Mailversand ###################################################################################### sub setCredentials { - my $hash = shift // carp "got no hash value !" && return; - my $ao = shift // carp "got no credentials type !" && return; - my $user = shift // carp "got no user name !" && return; - my $pass = shift // carp "got no password !" && return; + my $hash = shift // carp "got no hash value" && return; + my $ao = shift // carp "got no credentials type" && return; + my $user = shift // carp "got no user name" && return; + my $pass = shift // carp "got no password" && return; my $name = $hash->{NAME}; my $success; @@ -251,9 +261,9 @@ return ($success); # $ao = "SMTPcredentials" -> Credentials für Mailversand ###################################################################################### sub getCredentials { - my $hash = shift // carp "got no hash value !" && return; + my $hash = shift // carp "got no hash value" && return; my $boot = shift; - my $ao = shift // carp "got no credentials type !" && return; + my $ao = shift // carp "got no credentials type" && return; my $name = $hash->{NAME}; my ($success, $username, $passwd, $index, $retcode, $credstr); my (@key,$len,$i); @@ -322,8 +332,8 @@ return ($success, $username, $passwd); # Test ob JSON-String vorliegt ############################################################################### sub evaljson { - my $hash = shift // carp "got no hash value !" && return; - my $myjson = shift // carp "got no string for JSON test !" && return; + my $hash = shift // carp "got no hash value" && return; + my $myjson = shift // carp "got no string for JSON test" && return; my $OpMode = $hash->{OPMODE}; my $name = $hash->{NAME}; @@ -357,4 +367,275 @@ sub evaljson { return ($success,$myjson); } +#################################################################################### +# Login wenn keine oder ungültige Session-ID vorhanden ist +# $apiref = Referenz zum API Hash +# $fret = Rückkehrfunktion nach erfolgreichen Login +#################################################################################### +sub login { + my $hash = shift // carp "got no hash value" && return; + my $apiref = shift // carp "got no API reference" && return; + my $fret = shift // carp "got no return function reference" && return; + my $name = $hash->{NAME}; + my $serveraddr = $hash->{SERVERADDR}; + my $serverport = $hash->{SERVERPORT}; + my $apiauth = $apiref->{AUTH}{NAME}; + my $apiauthpath = $apiref->{AUTH}{PATH}; + my $apiauthver = $apiref->{AUTH}{VER}; + my $proto = $hash->{PROTOCOL}; + my $type = $hash->{TYPE}; + + my ($url,$param,$urlwopw); + + delete $hash->{HELPER}{SID}; + + Log3($name, 4, "$name - --- Begin Function login ---"); + + my ($success, $username, $password) = getCredentials($hash,0,"credentials"); # Credentials abrufen + + if (!$success) { + Log3($name, 2, "$name - Credentials couldn't be retrieved successfully - make sure you've set it with \"set $name credentials \""); + delActiveToken($hash) if($type eq "SSCam"); + return; + } + + my $lrt = AttrVal($name,"loginRetries",3); + + if($hash->{HELPER}{LOGINRETRIES} >= $lrt) { # Max Versuche erreicht -> login wird abgebrochen, Freigabe Funktionstoken + delActiveToken($hash) if($type eq "SSCam"); + Log3($name, 2, "$name - ERROR - Login or privilege of user $username unsuccessful"); + return; + } + + my $timeout = AttrVal($name,"timeout",60); # Kompatibilität zu Modulen die das Attr "timeout" verwenden + my $httptimeout = AttrVal($name,"httptimeout",$timeout); + $httptimeout = 60 if($httptimeout < 60); + Log3($name, 4, "$name - HTTP-Call login will be done with httptimeout-Value: $httptimeout s"); + + my $sid = AttrVal($name, "noQuotesForSID", 0) ? "sid" : qq{"sid"}; # sid in Quotes einschliessen oder nicht -> bei Problemen mit 402 - Permission denied + + if (AttrVal($name,"session","DSM") eq "DSM") { + $url = "$proto://$serveraddr:$serverport/webapi/$apiauthpath?api=$apiauth&version=$apiauthver&method=Login&account=$username&passwd=$password&format=$sid"; + $urlwopw = "$proto://$serveraddr:$serverport/webapi/$apiauthpath?api=$apiauth&version=$apiauthver&method=Login&account=$username&passwd=*****&format=$sid"; + + } else { + $url = "$proto://$serveraddr:$serverport/webapi/$apiauthpath?api=$apiauth&version=$apiauthver&method=Login&account=$username&passwd=$password&session=SurveillanceStation&format=$sid"; + $urlwopw = "$proto://$serveraddr:$serverport/webapi/$apiauthpath?api=$apiauth&version=$apiauthver&method=Login&account=$username&passwd=*****&session=SurveillanceStation&format=$sid"; + } + + my $printurl = AttrVal($name, "showPassInLog", 0) ? $url : $urlwopw; + + Log3($name, 4, "$name - Call-Out now: $printurl"); + $hash->{HELPER}{LOGINRETRIES}++; + + $param = { + url => $url, + timeout => $httptimeout, + hash => $hash, + user => $username, + funcret => $fret, + apiref => $apiref, + method => "GET", + header => "Accept: application/json", + callback => \&loginReturn + }; + + HttpUtils_NonblockingGet ($param); + +return; +} + +sub loginReturn { + my $param = shift; + my $err = shift; + my $myjson = shift; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + my $username = $param->{user}; + my $fret = $param->{funcret}; + my $apiref = $param->{apiref}; + my $type = $hash->{TYPE}; + + my $success; + + if ($err ne "") { # ein Fehler bei der HTTP Abfrage ist aufgetreten + Log3($name, 2, "$name - error while requesting ".$param->{url}." - $err"); + + readingsSingleUpdate($hash, "Error", $err, 1); + + return login($hash,$apiref,$fret); + + } elsif ($myjson ne "") { # wenn die Abfrage erfolgreich war ($data enthält die Ergebnisdaten des HTTP Aufrufes) + ($success) = evaljson($hash,$myjson); # Evaluiere ob Daten im JSON-Format empfangen wurden + if (!$success) { + Log3($name, 4, "$name - no JSON-Data returned: ".$myjson); + delActiveToken($hash) if($type eq "SSCam"); + return; + } + + my $data = decode_json($myjson); + + Log3($name, 5, "$name - JSON decoded: ". Dumper $data); + + $success = $data->{'success'}; + + if ($success) { # login war erfolgreich + my $sid = $data->{'data'}->{'sid'}; + + $hash->{HELPER}{SID} = $sid; # Session ID in hash eintragen + + readingsBeginUpdate ($hash); + readingsBulkUpdate ($hash,"Errorcode","none"); + readingsBulkUpdate ($hash,"Error","none"); + readingsEndUpdate ($hash, 1); + + Log3($name, 4, "$name - Login of User $username successful - SID: $sid"); + + return &$fret($hash); + + } else { + my $errorcode = $data->{'error'}->{'code'}; # Errorcode aus JSON ermitteln + my $error = expErrorsAuth($hash,$errorcode); # Fehlertext zum Errorcode ermitteln + + readingsBeginUpdate ($hash); + readingsBulkUpdate ($hash,"Errorcode",$errorcode); + readingsBulkUpdate ($hash,"Error",$error); + readingsEndUpdate ($hash, 1); + + Log3($name, 3, "$name - Login of User $username unsuccessful. Code: $errorcode - $error - try again"); + + return login($hash,$apiref,$fret); + } + } + +return login($hash,$apiref,$fret); +} + +################################################################################### +# Funktion logout +################################################################################### +sub logout { + my $hash = shift // carp "got no hash value" && return; + my $apiref = shift // carp "got no API reference" && return; + my $name = $hash->{NAME}; + my $serveraddr = $hash->{SERVERADDR}; + my $serverport = $hash->{SERVERPORT}; + my $apiauth = $apiref->{AUTH}{NAME}; + my $apiauthpath = $apiref->{AUTH}{PATH}; + my $apiauthver = $apiref->{AUTH}{VER}; + my $sid = $hash->{HELPER}{SID}; + my $proto = $hash->{PROTOCOL}; + + my $url; + + Log3($name, 4, "$name - --- Start Synology logout ---"); + + my $httptimeout = AttrVal($name,"httptimeout",4); + Log3($name, 5, "$name - HTTP-Call will be done with httptimeout-Value: $httptimeout s"); + + if (AttrVal($name,"session","DSM") eq "DSM") { + $url = "$proto://$serveraddr:$serverport/webapi/$apiauthpath?api=$apiauth&version=$apiauthver&method=Logout&_sid=$sid"; + + } else { + $url = "$proto://$serveraddr:$serverport/webapi/$apiauthpath?api=$apiauth&version=$apiauthver&method=Logout&session=SurveillanceStation&_sid=$sid"; + } + + my $param = { + url => $url, + timeout => $httptimeout, + hash => $hash, + method => "GET", + header => "Accept: application/json", + callback => \&logoutReturn + }; + + HttpUtils_NonblockingGet ($param); + +return; +} + +sub logoutReturn { + my $param = shift; + my $err = shift; + my $myjson = shift; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + my $sid = $hash->{HELPER}{SID}; + my $type = $hash->{TYPE}; + + my ($success, $username) = getCredentials($hash,0,"credentials"); + + if ($err ne "") { # wenn ein Fehler bei der HTTP Abfrage aufgetreten ist + Log3($name, 2, "$name - error while requesting ".$param->{url}." - $err"); + readingsSingleUpdate($hash, "Error", $err, 1); + + } elsif ($myjson ne "") { # wenn die Abfrage erfolgreich war ($data enthält die Ergebnisdaten des HTTP Aufrufes) + Log3($name, 4, "$name - URL-Call: ".$param->{url}); + + ($success) = evaljson($hash,$myjson); # Evaluiere ob Daten im JSON-Format empfangen wurden + + if (!$success) { + Log3($name, 4, "$name - Data returned: ".$myjson); + delActiveToken($hash) if($type eq "SSCam"); + return; + } + + my $data = decode_json($myjson); + + Log3($name, 4, "$name - JSON returned: ". Dumper $data); + + $success = $data->{'success'}; + + if ($success) { # die Logout-URL konnte erfolgreich aufgerufen werden + Log3($name, 2, "$name - Session of User \"$username\" terminated - session ID \"$sid\" deleted"); + + } else { + my $errorcode = $data->{'error'}->{'code'}; # Errorcode aus JSON ermitteln + my $error = expErrorsAuth($hash,$errorcode); # Fehlertext zum Errorcode ermitteln + + Log3($name, 2, "$name - ERROR - Logout of User $username was not successful, however SID: \"$sid\" has been deleted. Errorcode: $errorcode - $error"); + } + } + + delete $hash->{HELPER}{SID}; # Session-ID aus Helper-hash löschen + + delActiveToken($hash); # ausgeführte Funktion ist erledigt (auch wenn logout nicht erfolgreich), Freigabe Funktionstoken + + CancelDelayedShutdown($name); + +return; +} + +############################################################################################# +# Token setzen +############################################################################################# +sub setActiveToken { + my $hash = shift // carp "got no hash value" && return; + my $name = $hash->{NAME}; + + $hash->{HELPER}{ACTIVE} = "on"; + + if (AttrVal($name,"debugactivetoken",0)) { + Log3($name, 1, "$name - Active-Token set by OPMODE: $hash->{OPMODE}"); + } + +return; +} + +############################################################################################# +# Token freigeben +############################################################################################# +sub delActiveToken { + my $hash = shift // carp "got no hash value" && return; + my $name = $hash->{NAME}; + + $hash->{HELPER}{ACTIVE} = "off"; + + if (AttrVal($name,"debugactivetoken",0)) { + Log3($name, 1, "$name - Active-Token deleted by OPMODE: $hash->{OPMODE}"); + } + +return; +} + 1; \ No newline at end of file