From 010b8ef7c32fb77e236b6a0081251b7bb860cc53 Mon Sep 17 00:00:00 2001 From: aschwarz Date: Tue, 19 Dec 2023 13:24:32 +0100 Subject: [PATCH] first commit --- 70_Klafs.pm | 1775 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1775 insertions(+) create mode 100644 70_Klafs.pm diff --git a/70_Klafs.pm b/70_Klafs.pm new file mode 100644 index 0000000..b65e75d --- /dev/null +++ b/70_Klafs.pm @@ -0,0 +1,1775 @@ +# $Id: 70_Klafs.pm 26433 2022-09-20 15:32:58Z xasher $ +############################################################################## +# +# 70_Klafs.pm +# A FHEM Perl module to control a Klafs sauna. +# +# This file 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 . +# Forum: https://forum.fhem.de/index.php?topic=127701 +# +############################################################################## +package FHEM::Klafs; +use strict; +use warnings; + +sub ::Klafs_Initialize { goto &Initialize } + +use Carp qw(carp); +use Scalar::Util qw(looks_like_number); +use Time::HiRes qw(gettimeofday); +use JSON qw(decode_json encode_json); +use Time::Piece; +use Time::Local; +use HttpUtils; +use GPUtils qw(:all); +use FHEM::Core::Authentication::Passwords qw(:ALL); + +my %sets = ( + off => 'noArg', + password => '', + on => '', + ResetLoginFailures => '', + update => 'noArg', +); + +my %gets = ( + help => 'noArg', + SaunaID => 'noArg', + ); + +BEGIN { + + GP_Import(qw( + readingsBeginUpdate + readingsBulkUpdate + readingsEndUpdate + readingsSingleUpdate + Log3 + defs + init_done + InternalTimer + strftime + RemoveInternalTimer + readingFnAttributes + AttrVal + notifyRegexpChanged + ReadingsVal + HttpUtils_NonblockingGet + HttpUtils_BlockingGet + )) +}; + +################################### +sub Initialize { + my $hash = shift; + + Log3 ($hash, 5, 'Klafs_Initialize: Entering'); + $hash->{DefFn} = \&Define; + $hash->{UndefFn} = \&Undef; + $hash->{SetFn} = \&Set; + $hash->{AttrFn} = \&Attr; + $hash->{GetFn} = \&Get; + $hash->{RenameFn} = \&Rename; + $hash->{AttrList} = 'username saunaid pin interval disable:1,0 ' . $main::readingFnAttributes; + return; +} + +sub Attr +{ + my ( $cmd, $name, $attrName, $attrVal ) = @_; + my $hash = $defs{$name}; + + if( $attrName eq 'disable' ) { + RemoveInternalTimer($hash) if $cmd ne 'del'; + InternalTimer(gettimeofday(), \&Klafs_DoUpdate, $hash, 0) if $cmd eq 'del' || !$attrVal && $init_done; + }elsif( $attrName eq 'username' ) { + if( $cmd eq 'set' ) { + $hash->{Klafs}->{username} = $attrVal; + Log3 ($name, 3, "$name - username set to " . $hash->{Klafs}->{username}); + } + }elsif( $attrName eq 'saunaid' ) { + if( $cmd eq 'set' ) { + $hash->{Klafs}->{saunaid} = $attrVal; + Log3 ($name, 3, "$name - saunaid set to " . $hash->{Klafs}->{saunaid}); + } + }elsif( $attrName eq 'pin' ) { + if( $cmd eq 'set' ) { + return 'Pin is not a number!' if !looks_like_number($attrVal); + $hash->{Klafs}->{pin} = $attrVal; + Log3 ($name, 3, "$name - pin set to " . $hash->{Klafs}->{pin}); + } + }elsif( $attrName eq 'interval' ) { + if( $cmd eq 'set' ) { + return 'Interval must be greater than 0' if !$attrVal; + $hash->{Klafs}->{interval} = $attrVal; + InternalTimer( time() + $hash->{Klafs}->{interval}, \&Klafs_DoUpdate, $hash, 0 ); + Log3 ($name, 3, "$name - set interval: $attrVal"); + }elsif( $cmd eq 'del' ) { + $hash->{Klafs}->{interval} = 60; + InternalTimer( time() + $hash->{Klafs}->{interval}, \&Klafs_DoUpdate, $hash, 0 ); + Log3 ($name, 3, "$name - deleted interval and set to default: 60"); + } + } + return; +} + +################################### +sub Define { + my $hash = shift; + my $def = shift; + + return $@ if !FHEM::Meta::SetInternals($hash); + my @args = split m{\s+}, $def; + my $usage = qq (syntax: define Klafs); + return $usage if ( @args != 2 ); + my ( $name, $type ) = @args; + + Log3 ($name, 5, "Klafs $name: called function Klafs_Define()"); + + $hash->{NAME} = $name; + $hash->{helper}->{passObj} = FHEM::Core::Authentication::Passwords->new($hash->{TYPE}); + + readingsSingleUpdate( $hash, "last_errormsg", "0", 0 ); + Klafs_CONNECTED($hash,'initialized',1); + $hash->{Klafs}->{interval} = 60; + InternalTimer( time() + $hash->{Klafs}->{interval}, \&Klafs_DoUpdate, $hash, 0 ); + $hash->{Klafs}->{reconnect} = 0; + $hash->{Klafs}->{expire} = time(); + + InternalTimer(gettimeofday() + AttrVal($name,'interval',$hash->{Klafs}->{interval}), 'Klafs_DoUpdate', $hash, 0) if !$init_done; + notifyRegexpChanged($hash, 'global',1); + Klafs_DoUpdate($hash) if $init_done && !AttrVal($name,'disable',0); + + return; +} + +################################### +sub Undef { + my $hash = shift // return; + my $name = $hash->{NAME}; + Log3 ($name, 5, "Klafs $name: called function Klafs_Undefine()"); + + # De-Authenticate + Klafs_CONNECTED( $hash, 'deauthenticate',1 ); + + # Stop the internal GetStatus-Loop and exit + RemoveInternalTimer($hash); + + return; +} + +sub Rename +{ + my $name_new = shift // return; + my $name_old = shift // return; + + my $passObj = $main::defs{$name_new}->{helper}->{passObj}; + + my $password = $passObj->getReadPassword($name_old) // return; + + $passObj->setStorePassword($name_new, $password); + $passObj->setDeletePassword($name_old); + + return; +} + +sub Klafs_CONNECTED { + my $hash = shift // return; + my $set = shift; + my $notUseBulk = shift; + + if ($set) { + $hash->{Klafs}->{CONNECTED} = $set; + + if ( $notUseBulk ) { + readingsSingleUpdate($hash,'state',$set,1) if $set ne ReadingsVal($hash->{NAME},'state',''); + } else { + readingsBulkUpdate($hash,'state',$set) if $set ne ReadingsVal($hash->{NAME},'state',''); + } + return; + } + return 'disabled' if $hash->{Klafs}->{CONNECTED} eq 'disabled'; + return 1 if $hash->{Klafs}->{CONNECTED} eq 'connected'; + return 0; +} + +############################################################## +# +# API AUTHENTICATION +# +############################################################## + +sub Klafs_Auth{ + my ($hash) = @_; + my $name = $hash->{NAME}; + # $hash->{Klafs}->{reconnect}: Sperre bei Reconnect. Zwischen Connects müssen 300 Sekunden liegen. + # $hash->{Klafs}->{LoginFailures}: Anzahl fehlerhafte Logins. Muss 0 sein, sonst kein connect. Bei drei Fehlversuchen sperrt Klafs den Benutzer + + $hash->{Klafs}->{reconnect} = 0 if(!defined $hash->{Klafs}->{reconnect}); + my $LoginFailures = ReadingsVal( $name, "LoginFailures", "0" ); + + $hash->{Klafs}->{LoginFailures} //= ''; + if($hash->{Klafs}->{LoginFailures} eq ""){ + $hash->{Klafs}->{LoginFailures} = 0; + } + + if (time() >= $hash->{Klafs}->{reconnect}){ + Log3 ($name, 4, "Reconnect"); + + + my $username = $hash->{Klafs}->{username} // carp q[No username found!] && return; + my $password = $hash->{helper}->{passObj}->getReadPassword($name) // q{} && carp q[No password found!] && return;; + + + #Reading auslesen und definieren um das Reading unten zu schreiben. Intern wird $hash->{Klafs}->{LoginFailures}, weil Readings ggf. nicht schnell genug zur Verfuegung stehen. + my $LoginFailures = ReadingsVal( $name, "LoginFailures", "0" ); + + return if $hash->{Klafs}->{LoginFailures} > 0; + Log3 ($name, 4, "Anzahl Loginfailures: $hash->{Klafs}->{LoginFailures}"); + + if ( $hash->{Klafs}->{username} eq "") { + my $msg = "Missing attribute: attr $name username "; + Log3 ($name, 4, $msg); + return $msg; + }elsif ( $password eq "") { + my $msg = "Missing password: set $name password "; + Log3 ($name, 4, $msg); + return $msg; + }else{ + # Reconnects nicht unter 300 Sekunden durchführen + my $reconnect = time() + 300; + $hash->{Klafs}->{reconnect} = $reconnect; + my $header = "Content-Type: application/x-www-form-urlencoded\r\n". + "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\r\n". + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7r\n". + "Accept-Encoding: gzip, deflate, br\r\n". + "Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7"; + my $datauser = "UserName=$username&Password=$password&RememberMe=false"; + + if ($hash->{Klafs}->{LoginFailures} eq "0"){ + + HttpUtils_NonblockingGet({ + url => "https://sauna-app-19.klafs.com/Account/Login", + ignoreredirects => 1, + timeout => 5, + hash => $hash, + method => "POST", + header => $header, + data => $datauser, + callback => \&Klafs_AuthResponse, + }); + } + } + } + return; +} + + + +# Antwortheader aus dem Login auslesen fuer das Cookie +sub Klafs_AuthResponse { + my ($param, $err, $data) = @_; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + my $header = $param->{httpheader}; + Log3 ($name, 5, "header: $header"); + Log3 ($name, 5, "Data: $data"); + Log3 ($name, 5, "Error: $err"); + readingsBeginUpdate($hash); + if($data=~/
  • /) { + for my $err ($data =~ m /
    • ?(.*)<\/li>/) { + my %umlaute = ("ä" => "ae", "ü" => "ue", "Ä" => "Ae", "Ö" => "Oe", "ö" => "oe", "Ü" => "Ue", "ß" => "ss"); + my $umlautkeys = join ("|", keys(%umlaute)); + $err=~ s/($umlautkeys)/$umlaute{$1}/g; + Log3 ($name, 1, "Klafs $name: $err"); + $hash->{Klafs}->{LoginFailures} = $hash->{Klafs}->{LoginFailures}+1; + readingsBulkUpdate( $hash, 'last_errormsg', $err ); + readingsBulkUpdate( $hash, 'LoginFailures', $hash->{Klafs}->{LoginFailures}); + } + Klafs_CONNECTED($hash,'error'); + }else{ + readingsBulkUpdate( $hash, 'LoginFailures', 0, 0); + $hash->{Klafs}->{LoginFailures} =0; + for my $cookie ($header =~ m/Set-Cookie: ?(.*)/gi) { + $cookie =~ /([^,; ]+)=([^,;\s\v]+)[;,\s\v]*([^\v]*)/; + my $aspxauth = $1 . "=" .$2 .";"; + $hash->{Klafs}->{cookie} = $aspxauth; + Log3 ($name, 4, "$name: GetCookies parsed Cookie: $aspxauth"); + + # Cookie soll nach 2 Tagen neu erzeugt werden + my $expire = time() + 172800; + $hash->{Klafs}->{expire} = $expire; + my $expire_date = strftime("%Y-%m-%d %H:%M:%S", localtime($expire)); + readingsBulkUpdate( $hash, 'cookieExpire', $expire_date, 0 ); + + Klafs_CONNECTED($hash,'authenticated'); + } + } + readingsEndUpdate($hash,1); + return; +} + +############################################################## +# +# Cookie pruefen und Readings erneuern +# +############################################################## + +sub klafs_getStatus{ + my ($hash, $def) = @_; + my $name = $hash->{NAME}; + + my $LoginFailures = ReadingsVal( $name, "LoginFailures", "0" ); + if(!defined $hash->{Klafs}->{LoginFailures}){ + $hash->{Klafs}->{LoginFailures} = $LoginFailures; + } + + # SaunaIDs für GET zur Verfügung stellen + Klafs_GetSaunaIDs_Send($hash); + + + if ( $hash->{Klafs}->{saunaid} eq "") { + my $msg = "Missing attribute: attr $name saunaid -> Use to receive your SaunaID"; + Log3 ($name, 1, $msg); + return $msg; + } + + my $aspxauth = $hash->{Klafs}->{cookie}; + my $saunaid = $hash->{Klafs}->{saunaid}; + + my $header_gs = "Content-Type: application/json; charset=utf-8\r\n". + "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\r\n". + "Accept: text/plain, */*; q=0.01r\n". + "Accept-Encoding: gzip, deflate, br\r\n". + "Accept-Language: de,en;q=0.7,en-US;q=0.3\r\n". + "Cookie: $aspxauth"; + my $datauser_gs = '{"saunaId":"'.$saunaid.'"}'; +## +Log3 ($name, 5, "Status URL: https://sauna-app-19.klafs.com/SaunaApp/GetData?id=$saunaid"); +## + HttpUtils_NonblockingGet({ + url => "https://sauna-app-19.klafs.com/SaunaApp/GetData?id=$saunaid", + timeout => 5, + hash => $hash, + method => "POST", + header => $header_gs, + data => $datauser_gs, + callback => \&klafs_getStatusResponse, + }); + +## #Name Vorname Mail Benutzername +## #GET Anfrage mit ASPXAUTH +## my $header_user = "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\r\n". +## "Cookie: $aspxauth"; +## +## +## HttpUtils_NonblockingGet({ +## url => "https://sauna-app-19.klafs.com/Account/ChangeProfile", +## timeout => 5, +## hash => $hash, +## method => "GET", +## header => $header_user, +## callback => \&Klafs_GETProfile, +## }); + +## my $header_set = "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\r\n". +## "Cookie: $aspxauth"; +## +## HttpUtils_NonblockingGet({ +## url => "https://sauna-app-19.klafs.com/SaunaApp/ChangeSettings", +## timeout => 5, +## hash => $hash, +## method => "GET", +## header => $header_set, +## callback => \&Klafs_GETSettings, +## }); + return; +} + + + +sub klafs_getStatusResponse { + my ($param, $err, $data) = @_; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + my $header = $param->{httpheader}; + + Log3 ($name, 5, "Status header: $header"); + Log3 ($name, 5, "Status Data: $data"); + Log3 ($name, 5, "Status Error: $err"); + + if($data !~/Account\/Login/) { + # Wenn in $data eine Anmeldung verlangt wird und kein json kommt, darf es nicht weitergehen. + # Connect darf es hier nicht geben. Das darf nur an einer Stelle kommen. Sonst macht perl mehrere connects gleichzeitig- bei 3 Fehlversuchen wäre der Account gesperrt + + #my $return = decode_json( "$data" ); + my $entries; + if ( !eval { $entries = decode_json($data) ; 1 } ) { + #sonstige Fehlerbehandlungsroutinen hierher, dann ; + return Log3($name, 1, "JSON decoding error: $@"); + } + + # boolsche Werte in true/false uebernehmen + for my $key (qw( saunaSelected sanariumSelected irSelected isConnected isPoweredOn isReadyForUse showBathingHour)) { + $entries->{$key} = $entries->{$key} ? q{true} : q{false} ; + } + my $power = $entries->{isPoweredOn} eq q{true} ? 'on' + : $entries->{isPoweredOn} eq q{false} ? 'off' + : 0; + $entries->{power} = $power; + + $entries->{statusMessage} //= ''; + $entries->{currentTemperature} = '0' if $entries->{currentTemperature} eq '141'; + $entries->{RemainTime} = sprintf("%2.2d:%2.2d" , $entries->{bathingHours}, $entries->{bathingMinutes}); + my $modus = $entries->{saunaSelected} eq q{true} ? 'Sauna' + : $entries->{sanariumSelected} eq q{true} ? 'Sanarium' + : $entries->{irSelected} eq q{true} ? 'Infrared' + : 0; + $entries->{Mode} = $modus; + + # Loop ueber $entries und ggf. reading schreiben + my $old; + readingsBeginUpdate ($hash); + for my $record ($entries) { + for my $key (keys(%$record)) { + my $new = $record->{$key}; + # Alter Wert Readings auslesen + $old = ReadingsVal( $name, $key, "" ); + next if $old eq $new; + # Readings schreiben, wenn es einen anderen Wert hat + readingsBulkUpdate($hash, $key, $new); + } + } + + Klafs_CONNECTED($hash,'connected'); + readingsEndUpdate($hash, 1); + }else{ + # Wenn Account/Login zurück kommt, dann benötigt es einen reconnect + Klafs_CONNECTED($hash,'disconnected', 1); + } + return; +} + + + +sub Klafs_GETProfile { + my ($param, $err, $data) = @_; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + my $header = $param->{httpheader}; + Log3 ($name, 5, "Profile header: $header"); + Log3 ($name, 5, "Profile Data: $data"); + Log3 ($name, 5, "Profile Error: $err"); + + if($data !~/Account\/Login/) { + # Wenn in $data eine Anmeldung verlangt wird und kein json kommt, darf es nicht weitergehen. + # Connect darf es hier nicht geben. Das darf nur an einer Stelle kommen. Sonst macht perl mehrere connects gleichzeitig- bei 3 Fehlversuchen wäre der Account gesperrt + readingsBeginUpdate ($hash); + if($data=~/{hash}; + my $name = $hash->{NAME}; + my $header = $param->{httpheader}; + Log3 ($name, 5, "Settings header: $header"); + Log3 ($name, 5, "Settings Data: $data"); + Log3 ($name, 5, "Settings Error: $err"); + + if($data !~/Account\/Login/) { + # Wenn in $data eine Anmeldung verlangt wird und kein json kommt, darf es nicht weitergehen. + # Connect darf es hier nicht geben. Das darf nur an einer Stelle kommen. Sonst macht perl mehrere connects gleichzeitig- bei 3 Fehlversuchen wäre der Account gesperrt + if($data=~/StandByTime: parseInt\(\'/) { + readingsBeginUpdate ($hash); + for my $output ($data =~ m /StandByTime: parseInt\(\'?(.*)'/) { + my $sbtime = $1 eq q{24} ? '1 Tag' + : $1 eq q{72} ? '3 Tage' + : $1 eq q{168} ? '1 Woche' + : $1 eq q{672} ? '4 Wochen' + : $1 eq q{1344} ? '8 Wochen' + : 'Internal error'; + my $sbcloud = ReadingsVal( $name, 'standbytime', '' ); + if($sbcloud eq '' || $sbcloud ne $sbtime){ + readingsBulkUpdate( $hash, 'standbytime', $sbtime, 1 ); + } + } + readingsEndUpdate($hash, 1); + } + + if($data=~/Language: \'/) { + readingsBeginUpdate ($hash); + for my $output ($data =~ m /Language: \'?(.*)'/) { + my $language = $1 eq q{de} ? 'Deutsch' + : $1 eq q{en} ? 'Englisch' + : $1 eq q{fr} ? 'Franzoesisch' + : $1 eq q{es} ? 'Spanisch' + : $1 eq q{ru} ? 'Russisch' + : $1 eq q{pl} ? 'Polnisch' + : 'Internal error'; + my $langcloud = ReadingsVal( $name, 'langcloud', '' ); + if($langcloud eq '' || $langcloud ne $language){ + readingsBulkUpdate( $hash, 'langcloud', $language, 1 ); + } + } + readingsEndUpdate($hash, 1); + } + }else{ + # Wenn Account/Login zurück kommt, dann benötigt es einen reconnect + Klafs_CONNECTED($hash,'disconnected', 1); + } + return; +} + + +################################### +sub Get { + my ( $hash, @a ) = @_; + + my $name = $hash->{NAME}; + my $what; + Log3 ($name, 5, "Klafs $name: called function Klafs_Get()"); + + return "argument is missing" if ( @a < 2 ); + + $what = $a[1]; + + + return _Klafs_help($hash) if ( $what =~ /^(help)$/ ); + return _Klafs_saunaid($hash) if ( $what =~ /^(SaunaID)$/ ); + return "$name get with unknown argument $what, choose one of " . join(" ", sort keys %gets); +} + +sub _Klafs_help { + return << 'EOT'; +------------------------------------------------------------------------------------------------------------------------------------------------------------ +| Set Parameter | +------------------------------------------------------------------------------------------------------------------------------------------------------------ +|on | ohne Parameter -> Default Sauna 90 Grad | +| | set "name" on Sauna 90 - 3 Parameter: Sauna mit Temperatur [10-100]; Optional Uhrzeit [19:30] | +| | set "name" on Saunarium 65 5 - 4 Parameter: Sanarium mit Temperatur [40-75]; Optional HumidtyLevel [0-10] und Uhrzeit [19:30] | +| | set "name" on Infrared 30 5 - 4 Parameter: Infrarot mit Temperatur [20-40] und IR Level [0-10]; Optional Uhrzeit [19:30] | +| | Infrarot ist nicht supported, da keine Testumgebung verfuegbar. | +------------------------------------------------------------------------------------------------------------------------------------------------------------ +|off | Schaltet die Sauna|Sanarium|Infrarot aus - ohne Parameter. | +------------------------------------------------------------------------------------------------------------------------------------------------------------ +|ResetLoginFailures | Bei fehlerhaftem Login wird das Reading LoginFailures auf 1 gesetzt. Damit ist der automatische Login vom diesem Modul gesperrt. | +| | Klafs sperrt den Account nach 3 Fehlversuchen. Damit nicht automatisch 3 falsche Logins hintereinander gemacht werden. | +| | ResetLoginFailures setzt das Reading wieder auf 0. Davor sollte man sich erfolgreich an der App bzw. unter sauna-app.klafs.com | +| | angemeldet bzw. das Passwort zurueckgesetzt haben. Erfolgreicher Login resetet die Anzahl der Fehlversuche in der Klafs-Cloud. | +------------------------------------------------------------------------------------------------------------------------------------------------------------ +|update | Refresht die Readings und fuehrt ggf. ein Login durch. | +------------------------------------------------------------------------------------------------------------------------------------------------------------ +| Get Parameter | +------------------------------------------------------------------------------------------------------------------------------------------------------------ +|SaunaID | Liest die verfuegbaren SaunaIDs aus. | +------------------------------------------------------------------------------------------------------------------------------------------------------------ +|help | Diese Hilfe | +------------------------------------------------------------------------------------------------------------------------------------------------------------ +EOT +} + +sub Klafs_GetSaunaIDs_Send{ + my ($hash) = @_; + my ($name,$self) = ($hash->{NAME},Klafs_Whoami()); + my $aspxauth = $hash->{Klafs}->{cookie}; + return if $hash->{Klafs}->{LoginFailures} > 0; + Log3 ($name, 5, "$name ($self) - executed1."); + + my $header = "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\r\n". + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\n". + "Accept-Encoding: gzip, deflate, br\r\n". + "Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7\r\n". + "Cookie: $aspxauth"; + HttpUtils_NonblockingGet({ + url => "https://sauna-app-19.klafs.com/SaunaApp/ChangeSettings", + timeout => 5, + hash => $hash, + method => "GET", + header => $header, + callback => \&Klafs_GetSaunaIDs_Receive, + }); + return; +} + +sub Klafs_GetSaunaIDs_Receive { + my ($param, $err, $data) = @_; + my ($name,$self,$hash) = ($param->{hash}->{NAME},Klafs_Whoami(),$param->{hash}); + my $returnwert1; + my $returnwert2; + + Log3 ($name, 5, "$name ($self) - executed2."); + + if ($err ne "") { + Log3 ($name, 4, "$name ($self) - error."); + } + elsif ($data ne "") { + if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) { + if($data !~/Account\/Login/) { + # Wenn in $data eine Anmeldung verlangt wird und keine Daten, darf es nicht weitergehen. + # Connect darf es hier nicht geben. Das darf nur an einer Stelle kommen. Sonst macht perl mehrere connects gleichzeitig - bei 3 Fehlversuchen wäre der Account gesperrt + $returnwert1 = ""; + $returnwert2 = ""; + if($data=~//) { + for my $output ($data =~ m /(.*?)<\/tr>/gis) { + $output=~ m/