diff --git a/2023-12-20-70_Klafs.pm b/2023-12-20-70_Klafs.pm new file mode 100644 index 0000000..7ba50fe --- /dev/null +++ b/2023-12-20-70_Klafs.pm @@ -0,0 +1,1761 @@ +# $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/59.0.3071.71 Safari/537.36"; + my $datauser = "UserName=$username&Password=$password"; + + if ($hash->{Klafs}->{LoginFailures} eq "0"){ + + HttpUtils_NonblockingGet({ + url => "https://sauna-app.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\r\n". + "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.71 Safari/537.36\r\n". + "Cookie: $aspxauth"; + my $datauser_gs = '{"saunaId":"'.$saunaid.'"}'; + + HttpUtils_NonblockingGet({ + url => "https://sauna-app.klafs.com/Control/GetSaunaStatus", + 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/59.0.3071.71 Safari/537.36\r\n". + "Cookie: $aspxauth"; + + HttpUtils_NonblockingGet({ + url => "https://sauna-app.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/59.0.3071.71 Safari/537.36\r\n". + "Cookie: $aspxauth"; + + HttpUtils_NonblockingGet({ + url => "https://sauna-app.klafs.com/Control/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) - executed."); + + my $header = "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.71 Safari/537.36\r\n". + "Cookie: $aspxauth"; + HttpUtils_NonblockingGet({ + url => "http://sauna-app.klafs.com/Control", + 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 $returnwerte; + + Log3 ($name, 5, "$name ($self) - executed."); + + 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 + $returnwerte = ""; + if($data=~//) { + for my $output ($data =~ m /(.*?)<\/tr>/gis) { + $output=~ m/(.*?)<\/span>/g; + $returnwerte .= $1.": "; + $output=~ m/
      {Klafs}->{GetSaunaIDs} = $returnwerte; + } + } + } + } + return; +} + +sub _Klafs_saunaid { + my ( $hash, @a ) = @_; + my $name = $hash->{NAME}; + + return "======================================== FOUND SAUNA-IDs ========================================\n" + . $hash->{Klafs}->{GetSaunaIDs} . + "================================================================================================="; + +} + + +################################### +sub Set { + my ( $hash, $name, $cmd, @args ) = @_; + return if $hash->{Klafs}->{LoginFailures} > 0 and !$cmd; + + + if (Klafs_CONNECTED($hash) eq 'disabled' && $cmd !~ /clear/) { + Log3 ($name, 3, "$name: set called with $cmd but device is disabled!") if ($cmd ne "?"); + return "Unknown argument $cmd, choose one of clear:all,readings"; + } + + my $temperature; + my $level; + my $power = ReadingsVal( $name, "power", "off" ); + + + # Klafs rundet bei der Startzeit immer auf volle 10 Minuten auf. Das ist der Zeitpunkt, wann die Sauna fertig aufgeheizt sein soll. Naechste 10 Minuten heisst also sofort aufheizen + my $FIFTEEN_MINS = (15 * 60); + my $now = time; + if (my $diff = $now % $FIFTEEN_MINS) { + $now += $FIFTEEN_MINS - $diff; + } + my $next = scalar localtime $now; + # doppelte Leerzeichen bei einstelligen Datumsangaben entfernen + $next =~ tr/ //s; + my @Zeit = split(/ /,$next); + my @Uhrzeit = split(/:/,$Zeit[3]); + my $std = $Uhrzeit[0]; + my $min = $Uhrzeit[1]; + # print "Decoded Zeit:\n".Dumper(@Zeit); + #Decoded Zeit: + #$VAR1 = 'Mon'; + #$VAR2 = 'Jun'; + #$VAR3 = '20'; + #$VAR4 = '15:15:00'; + #$VAR5 = '2022'; + + if($std < 10){ + if(substr($std,0,1) eq "0"){ + $std = substr($std,1,1); + } + } + if($min < 10){ + if(substr($min,0,1) eq "0"){ + $min = substr($min,1,1); + } + } + + + # on () + if ( $cmd eq "on" ) { + Log3 ($name, 2, "Klafs set $name " . $cmd); + + klafs_getStatus($hash); + my $mode = shift @args; + my $aspxauth = $hash->{Klafs}->{cookie}; + + my $pin = $hash->{Klafs}->{pin}; + my $saunaid = $hash->{Klafs}->{saunaid}; + my $selectedSaunaTemperature = ReadingsVal( $name, "selectedSaunaTemperature", "90" ); + my $selectedSanariumTemperature = ReadingsVal( $name, "selectedSanariumTemperature", "65" ); + my $selectedIrTemperature = ReadingsVal( $name, "selectedIrTemperature", "0" ); + my $selectedHumLevel = ReadingsVal( $name, "selectedHumLevel", "5" ); + my $selectedIrLevel = ReadingsVal( $name, "selectedIrLevel", "0" ); + my $isConnected = ReadingsVal( $name, "isConnected", "true" ); + my $isPoweredOn = ReadingsVal( $name, "isPoweredOn", "false" ); + my $isReadyForUse = ReadingsVal( $name, "isReadyForUse", "false" ); + my $currentTemperature = ReadingsVal( $name, "currentTemperature", "141" ); + if($currentTemperature eq "0"){ + $currentTemperature = "141"; + } + my $currentHumidity = ReadingsVal( $name, "currentHumidity", "0" ); + my $statusCode = ReadingsVal( $name, "statusCode", "0" ); + my $statusMessage = ReadingsVal( $name, "statusMessage", "" ); + if($statusMessage eq ""){ + $statusMessage = 'null'; + } + my $showBathingHour = ReadingsVal( $name, "showBathingHour", "false" ); + my $bathingHours = ReadingsVal( $name, "bathingHours", "0" ); + my $bathingMinutes = ReadingsVal( $name, "bathingMinutes", "0" ); + my $currentHumidityStatus = ReadingsVal( $name, "currentHumidityStatus", "0" ); + my $currentTemperatureStatus = ReadingsVal( $name, "currentTemperatureStatus", "0" ); + + if ( $pin eq "") { + my $msg = "Missing attribute: attr $name pin "; + Log3 ($name, 1, $msg); + return $msg; + }elsif ( $saunaid eq "") { + my $msg = "Missing attribute: attr $name $saunaid "; + Log3 ($name, 1, $msg); + return $msg; + }else{ + my $datauser_cv = ""; + if ( $mode eq "Sauna"){ + # Sauna hat 1 Parameter: Temperatur + #return "Zu wenig Argumente: Temperatur fehlt" if ( @args < 1 ); + my $temperature = shift @args; + if(!looks_like_number($temperature)){ + return "Geben Sie einen nummerischen Wert fuer ein"; + } + if ($temperature >= 10 && $temperature <=100 && $temperature ne ""){ + # Wenn Temperatur zwischen 10 und 100 Grad angegeben wurde: Werte aus der App entnommen + $temperature = $temperature; + }else{ + # Keine Temperatur oder ausser Range, letzter Wert auslesen ggf. auf 90 Grad setzen + $temperature = ReadingsVal( $name, "selectedSaunaTemperature", "" ); + if ($temperature eq "" || $temperature eq 0){ + $temperature = 90; + } + } + my $Time; + $Time = shift @args; + + if(!defined($Time)){ + $Time ="$Uhrzeit[0]:$Uhrzeit[1]"; + } + + if($Time =~ /:/){ + my @Timer = split(/:/,$Time); + $std = $Timer[0]; + $min = $Timer[1]; + if($std < 10){ + if(substr($std,0,1) eq "0"){ + $std = substr($std,1,1); + } + } + if($min < 10){ + + if(substr($min,0,1) eq "0"){ + $min = substr($min,1,1); + } + } + } + if ($std <0 || $std >23 || $min <0 || $min >59){ + return "Checken Sie das Zeitformat $std:$min\n"; + } + $datauser_cv = '{"changedData":{"saunaId":"'.$saunaid.'","saunaSelected":true,"sanariumSelected":false,"irSelected":false,"selectedSaunaTemperature":'.$temperature.',"selectedSanariumTemperature":'.$selectedSanariumTemperature.',"selectedIrTemperature":'.$selectedIrTemperature.',"selectedHumLevel":'.$selectedHumLevel.',"selectedIrLevel":'.$selectedIrLevel.',"selectedHour":'.$std.',"selectedMinute":'.$min.',"isConnected":'.$isConnected.',"isPoweredOn":'.$isPoweredOn.',"isReadyForUse":'.$isReadyForUse.',"currentTemperature":'.$currentTemperature.',"currentHumidity":'.$currentHumidity.',"statusCode":'.$statusCode.',"statusMessage":'.$statusMessage.',"showBathingHour":'.$showBathingHour.',"bathingHours":'.$bathingHours.',"bathingMinutes":'.$bathingMinutes.',"currentHumidityStatus":'.$currentHumidityStatus.',"currentTemperatureStatus":'.$currentTemperatureStatus.'}}'; + }elsif ( $mode eq "Sanarium" ) { + my $temperature = shift @args; + + + if(!looks_like_number($temperature)){ + return "Geben Sie einen nummerischen Wert fuer ein"; + } + if ($temperature >= 40 && $temperature <=75 && $temperature ne ""){ + $temperature = $temperature; + }else{ + # Letzer Wert oder Standardtemperatur + $temperature = ReadingsVal( $name, "selectedSanariumTemperature", "" ); + if ($temperature eq "" || $temperature eq 0){ + $temperature = 65; + } + + } + my $Time; + my $level; + $level = shift @args; + $Time = shift @args; + + if(!defined($Time)){ + $Time ="$Uhrzeit[0]:$Uhrzeit[1]"; + } + + # Parameter level ist optional. Wird in der ersten Variable eine anstelle des Levels eine Uhrzeit gefunden, dann level auf "" setzen und $std,$min setzen + if($level =~ /:/ || $Time =~ /:/){ + if($level =~ /:/){ + my @Timer = split(/:/,$level); + $std = $Timer[0]; + $min = $Timer[1]; + if($std < 10){ + if(substr($std,0,1) eq "0"){ + $std = substr($std,1,1); + } + } + if($min < 10){ + if(substr($min,0,1) eq "0"){ + $min = substr($min,1,1); + } + } + $level = ""; + }else{ + my @Timer = split(/:/,$Time); + $std = $Timer[0]; + $min = $Timer[1]; + if($std < 10){ + if(substr($std,0,1) eq "0"){ + $std = substr($std,1,1); + } + } + if($min < 10){ + if(substr($min,0,1) eq "0"){ + $min = substr($min,1,1); + } + } + } + } + if ($std <0 || $std >23 || $min <0 || $min >59){ + return "Checken Sie das Zeitformat $std:$min\n"; + } + + # Auf volle 10 Minuten runden + #if( substr($min,-1,1) > 0){ + # my $min1 = substr($min,0,1)+1; + # $min = $min1."0"; + # if($min eq 60){ + # $min = "00"; + # $std = $std+1; + # if($std eq 24){ + # $std = "00"; + # } + # } + #} + + if ($level >= 0 && $level <=10 && $level ne ""){ + $level = $level; + }else{ + # Letzer Wert oder Standardlevel + $level = ReadingsVal( $name, "selectedHumLevel", "" ); + if ($level eq ""){ + $level = 5; + } + + } + $datauser_cv = '{"changedData":{"saunaId":"'.$saunaid.'","saunaSelected":false,"sanariumSelected":true,"irSelected":false,"selectedSaunaTemperature":'.$selectedSaunaTemperature.',"selectedSanariumTemperature":'.$temperature.',"selectedIrTemperature":'.$selectedIrTemperature.',"selectedHumLevel":'.$level.',"selectedIrLevel":'.$selectedIrLevel.',"selectedHour":'.$std.',"selectedMinute":'.$min.',"isConnected":'.$isConnected.',"isPoweredOn":'.$isPoweredOn.',"isReadyForUse":'.$isReadyForUse.',"currentTemperature":'.$currentTemperature.',"currentHumidity":'.$currentHumidity.',"statusCode":'.$statusCode.',"statusMessage":'.$statusMessage.',"showBathingHour":'.$showBathingHour.',"bathingHours":'.$bathingHours.',"bathingMinutes":'.$bathingMinutes.',"currentHumidityStatus":'.$currentHumidityStatus.',"currentTemperatureStatus":'.$currentTemperatureStatus.'}}'; + }elsif ( $mode eq "Infrared" ) { + my $temperature = shift @args; + if(!looks_like_number($temperature)){ + return "Geben Sie einen nummerischen Wert fuer ein"; + } + if ($temperature >= 20 && $temperature <=40 && $temperature ne ""){ + $temperature = $temperature; + }else{ + # Letzer Wert oder Standardtemperatur + $temperature = ReadingsVal( $name, "selectedIrTemperature", "" ); + if ($temperature eq "" || $temperature eq 0){ + $temperature = 35; + } + } + my $Time; + my $level; + $level = shift @args; + $Time = shift @args; + + if(!defined($Time)){ + $Time ="$Uhrzeit[0]:$Uhrzeit[1]"; + } + + # Parameter level ist optional. Wird in der ersten Variable eine anstelle des Levels eine Uhrzeit gefunden, dann level auf "" setzen und $std,$min setzen + if($level =~ /:/ || $Time =~ /:/){ + if($level =~ /:/){ + my @Timer = split(/:/,$level); + $std = $Timer[0]; + $min = $Timer[1]; + if($std < 10){ + if(substr($std,0,1) eq "0"){ + $std = substr($std,1,1); + } + } + if($min < 10){ + if(substr($min,0,1) eq "0"){ + $min = substr($min,1,1); + } + } + $level = ""; + }else{ + my @Timer = split(/:/,$Time); + $std = $Timer[0]; + $min = $Timer[1]; + if($std < 10){ + if(substr($std,0,1) eq "0"){ + $std = substr($std,1,1); + } + } + if($min < 10){ + if(substr($min,0,1) eq "0"){ + $min = substr($min,1,1); + } + } + } + } + if ($std <0 || $std >23 || $min <0 || $min >59){ + return "Checken Sie das Zeitformat $std:$min\n"; + } + + if ($level >= 0 && $level <=10 && $level ne "" ){ + $level = $level; + }else{ + # Letzer Wert oder Standardlevel + $level = ReadingsVal( $name, "selectedIrLevel", "" ); + if ($level eq ""){ + $level = 5; + } + } + $datauser_cv = '{"changedData":{"saunaId":"'.$saunaid.'","saunaSelected":false,"sanariumSelected":false,"irSelected":true,"selectedSaunaTemperature":'.$selectedSaunaTemperature.',"selectedSanariumTemperature":'.$selectedSanariumTemperature.',"selectedIrTemperature":'.$temperature.',"selectedHumLevel":'.$selectedHumLevel.',"selectedIrLevel":'.$level.',"selectedHour":'.$std.',"selectedMinute":'.$min.',"isConnected":'.$isConnected.',"isPoweredOn":'.$isPoweredOn.',"isReadyForUse":'.$isReadyForUse.',"currentTemperature":'.$currentTemperature.',"currentHumidity":'.$currentHumidity.',"statusCode":'.$statusCode.',"statusMessage":'.$statusMessage.',"showBathingHour":'.$showBathingHour.',"bathingHours":'.$bathingHours.',"bathingMinutes":'.$bathingMinutes.',"currentHumidityStatus":'.$currentHumidityStatus.',"currentTemperatureStatus":'.$currentTemperatureStatus.'}}'; + + }else{ + $datauser_cv = '{"changedData":{"saunaId":"'.$saunaid.'","saunaSelected":true,"sanariumSelected":false,"irSelected":false,"selectedSaunaTemperature":90,"selectedSanariumTemperature":'.$selectedSanariumTemperature.',"selectedIrTemperature":'.$selectedIrTemperature.',"selectedHumLevel":'.$selectedHumLevel.',"selectedIrLevel":'.$selectedIrLevel.',"selectedHour":'.$std.',"selectedMinute":'.$min.',"isConnected":'.$isConnected.',"isPoweredOn":'.$isPoweredOn.',"isReadyForUse":'.$isReadyForUse.',"currentTemperature":'.$currentTemperature.',"currentHumidity":'.$currentHumidity.',"statusCode":'.$statusCode.',"statusMessage":'.$statusMessage.',"showBathingHour":'.$showBathingHour.',"bathingHours":'.$bathingHours.',"bathingMinutes":'.$bathingMinutes.',"currentHumidityStatus":'.$currentHumidityStatus.',"currentTemperatureStatus":'.$currentTemperatureStatus.'}}'; + } + + Log3 ($name, 4, "$name - JSON ON: $datauser_cv"); + # 1) Werte aendern + #print "Mode: ". $mode . " Temperature: ". $temperature . " Level: " .$level ."\n$datauser_cv\n\n"; + my $header_cv = "Content-Type: application/json\r\n". + "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.71 Safari/537.36\r\n". + "Cookie: $aspxauth"; + HttpUtils_BlockingGet({ + url => "https://sauna-app.klafs.com//Control/PostConfigChange", + timeout => 5, + hash => $hash, + method => "POST", + header => $header_cv, + data => $datauser_cv, + }); + + + my $state_onoff = ReadingsVal( $name, "isPoweredOn", "false" ); + + # Einschalten, wenn Sauna aus ist. + if($state_onoff eq "false"){ + my $header_af = "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.71 Safari/537.36\r\n". + "Cookie: $aspxauth"; + my $datauser_af = "s=$saunaid"; + # 2 Steps: 2) Antiforgery erzeugen; 3) Einschalten + HttpUtils_NonblockingGet({ + url => "https://sauna-app.klafs.com/Control/EnterPin", + timeout => 5, + hash => $hash, + method => "POST", + header => $header_af, + data => $datauser_af, + callback=>sub($$$){ + 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); + for my $cookie ($header =~ m/set-cookie: ?(.*)/gi) { + $cookie =~ /([^,; ]+)=([^,;\s\v]+)[;,\s\v]*([^\v]*)/; + my $antiforgery = $1 . "=" .$2 .";"; + my $antiforgery_date = strftime("%Y-%m-%d %H:%M:%S", localtime(time())); + readingsBulkUpdate( $hash, "antiforgery_date", "$antiforgery_date", 1 ); + Log3 ($name, 5, "$name: Antiforgery found: $antiforgery"); + $hash->{Klafs}->{antiforgery} = $antiforgery; + } + readingsEndUpdate($hash, 1); + + # 2) Einschalten + my $headeron = "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.71 Safari/537.36\r\n". + "Cookie: $aspxauth"; + my $antiforgery = $hash->{Klafs}->{antiforgery}; + my $datauseron = "$antiforgery&Pin=$pin&saunaId=$saunaid"; + HttpUtils_NonblockingGet({ + url => "https://sauna-app.klafs.com/Control/EnterPin", + timeout => 5, + hash => $hash, + method => "POST", + header => $headeron, + data => $datauseron, + callback => sub($$$){ + my ($param, $err, $data) = @_; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + Log3 ($name, 5, "header: $header"); + Log3 ($name, 5, "Data: $data"); + Log3 ($name, 5, "Error: $err"); + if($data=~/
      • /) { + readingsBeginUpdate ($hash); + 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"); + readingsBulkUpdate( $hash, "last_errormsg", "$err", 1 ); + } + readingsEndUpdate($hash, 1); + }else{ + $power = "on"; + Log3 ($name, 3, "Sauna on"); + readingsBeginUpdate ($hash); + readingsBulkUpdate( $hash, "power", $power, 1 ); + readingsBulkUpdate( $hash, "last_errormsg", "0", 1 ); + readingsEndUpdate($hash, 1); + klafs_getStatus($hash); + } + } + }); + } + }); + } + } + + # sauna off + }elsif ( $cmd eq "off" ) { + Log3 ($name, 2, "Klafs set $name " . $cmd); + klafs_getStatus($hash); + + my $aspxauth = $hash->{Klafs}->{cookie}; + + my $saunaid = $hash->{Klafs}->{saunaid}; + my $saunaSelected = ReadingsVal( $name, "saunaSelected", "true" ); + my $sanariumSelected = ReadingsVal( $name, "sanariumSelected", "false" ); + my $irSelected = ReadingsVal( $name, "irSelected", "false" ); + + my $selectedSaunaTemperature = ReadingsVal( $name, "selectedSaunaTemperature", "90" ); + my $selectedSanariumTemperature = ReadingsVal( $name, "selectedSanariumTemperature", "65" ); + my $selectedIrTemperature = ReadingsVal( $name, "selectedIrTemperature", "0" ); + my $selectedHumLevel = ReadingsVal( $name, "selectedHumLevel", "5" ); + my $selectedIrLevel = ReadingsVal( $name, "selectedIrLevel", "0" ); + my $selectedHour = ReadingsVal( $name, "selectedHour", "0" ); + my $selectedMinute = ReadingsVal( $name, "selectedMinute", "0" ); + + my $isConnected = ReadingsVal( $name, "isConnected", "true" ); + my $isPoweredOn = ReadingsVal( $name, "isPoweredOn", "false" ); + my $isReadyForUse = ReadingsVal( $name, "isReadyForUse", "false" ); + my $currentTemperature = ReadingsVal( $name, "currentTemperature", "141" ); + if($currentTemperature eq "0"){ + $currentTemperature = "141"; + } + my $currentHumidity = ReadingsVal( $name, "currentHumidity", "0" ); + my $statusCode = ReadingsVal( $name, "statusCode", "0" ); + my $statusMessage = ReadingsVal( $name, "statusMessage", "" ); + if($statusMessage eq ""){ + $statusMessage = 'null'; + } + my $showBathingHour = ReadingsVal( $name, "showBathingHour", "false" ); + my $bathingHours = ReadingsVal( $name, "bathingHours", "0" ); + my $bathingMinutes = ReadingsVal( $name, "bathingMinutes", "0" ); + my $currentHumidityStatus = ReadingsVal( $name, "currentHumidityStatus", "0" ); + my $currentTemperatureStatus = ReadingsVal( $name, "currentTemperatureStatus", "0" ); + + if ($saunaid eq ""){ + my $msg = "Missing attribute: attr $name saunaid "; + Log3 ($name, 1, $msg); + return $msg; + }else{ + + my $header = "Content-Type: application/json\r\n". + "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.71 Safari/537.36\r\n". + "Cookie: $aspxauth"; + + my $datauser_end = '{"changedData":{"saunaId":"'.$saunaid.'","saunaSelected":'.$saunaSelected.',"sanariumSelected":'.$sanariumSelected.',"irSelected":'.$irSelected.',"selectedSaunaTemperature":'.$selectedSaunaTemperature.',"selectedSanariumTemperature":'.$selectedSanariumTemperature.',"selectedIrTemperature":'.$selectedIrTemperature.',"selectedHumLevel":'.$selectedHumLevel.',"selectedIrLevel":'.$selectedIrLevel.',"selectedHour":'.$selectedHour.',"selectedMinute":'.$selectedMinute.',"isConnected":'.$isConnected.',"isPoweredOn":'.$isPoweredOn.',"isReadyForUse":'.$isReadyForUse.',"currentTemperature":'.$currentTemperature.',"currentHumidity":'.$currentHumidity.',"statusCode":'.$statusCode.',"statusMessage":'.$statusMessage.',"showBathingHour":'.$showBathingHour.',"bathingHours":'.$bathingHours.',"bathingMinutes":'.$bathingMinutes.',"currentHumidityStatus":'.$currentHumidityStatus.',"currentTemperatureStatus":'.$currentTemperatureStatus.'}}'; + Log3 ($name, 4, "$name - JSON_OFF: $datauser_end"); + + HttpUtils_BlockingGet({ + url => "https://sauna-app.klafs.com/Control/PostPowerOff", + timeout => 5, + hash => $hash, + method => "POST", + header => $header, + data => $datauser_end, + }); + + HttpUtils_BlockingGet({ + url => "https://sauna-app.klafs.com//Control/PostConfigChange", + timeout => 5, + hash => $hash, + method => "POST", + header => $header, + data => $datauser_end, + }); + $power = "off"; + readingsBeginUpdate ($hash); + readingsBulkUpdate( $hash, "power", $power, 1 ); + readingsEndUpdate($hash, 1); + Log3 ($name, 3, "Sauna off"); + } + }elsif ( $cmd eq "update" ) { + Klafs_DoUpdate($hash); + }elsif ( $cmd eq "ResetLoginFailures" ) { + readingsBeginUpdate ($hash); + readingsBulkUpdate( $hash, "LoginFailures", "0", 1 ); + readingsEndUpdate($hash, 1); + $hash->{Klafs}->{LoginFailures} =0; + }elsif($cmd eq 'password'){ + + my $password = shift @args; + print "$name - Passwort1: ".$password."\n"; + my ($res, $error) = defined $password ? $hash->{helper}->{passObj}->setStorePassword($name, $password) : $hash->{helper}->{passObj}->setDeletePassword($name); + + if(defined $error && !defined $res) + { + Log3($name, 1, "$name - could not update password"); + return "Error while updating the password - $error"; + }else{ + Log3($name, 1, "$name - password successfully saved"); + } + return; + }else{ + return "Unknown argument $cmd, choose one of " + . join( " ", + map { "$_" . ( $sets{$_} ? ":$sets{$_}" : "" ) } keys %sets ); + } +return; +} + + +############################################################## +# +# UPDATE FUNCTIONS +# +############################################################## + +sub Klafs_Whoami() { return (split('::',(caller(1))[3]))[1] || ''; } +sub Klafs_Whowasi() { return (split('::',(caller(2))[3]))[1] || ''; } + +sub Klafs_DoUpdate { + my ($hash) = @_; + my ($name,$self) = ($hash->{NAME},Klafs_Whoami()); + Log3 ($name, 5, "$name Klafs_DoUpdate() called."); + + RemoveInternalTimer($hash); + if (Klafs_CONNECTED($hash) eq 'disabled') { + Log3 ($name, 3, "$name - Device is disabled."); + return; + } + + InternalTimer(time() + $hash->{Klafs}->{interval}, \&Klafs_DoUpdate, $hash, 0); + if (time() >= $hash->{Klafs}->{expire} && $hash->{Klafs}->{CONNECTED} ne "disconnected" && $hash->{Klafs}->{CONNECTED} ne "initialized") { + Log3 ($name, 2, "$name - LOGIN TOKEN MISSING OR EXPIRED - Klafs_DoUpdate"); + Klafs_CONNECTED($hash,'disconnected',1); + + } elsif ($hash->{Klafs}->{CONNECTED} eq 'connected') { + Log3 ($name, 4, "$name - Update with device: " . $hash->{Klafs}->{saunaid}); + klafs_getStatus($hash); + } elsif ($hash->{Klafs}->{CONNECTED} eq 'disconnected' || $hash->{Klafs}->{CONNECTED} eq "initialized") { + # Das übernimmt eigentlich das notify unten. Hier wird es gebraucht, wenn innerhalb 5 Minuten nach den letzten Reconnect die Verbindung abbricht, dann muss der Login das Klafs_DoUpdate übernehmen + # Login wird 5 Minuten nach den letzten Login verhindert vom Modul. + Log3 ($name, 4, "$name - Reconnect within 5 Minutes"); + Klafs_Auth($hash); + } elsif ($hash->{Klafs}->{CONNECTED} eq 'authenticated') { + Log3 ($name, 4, "$name - Update with device: " . $hash->{Klafs}->{saunaid}); + klafs_getStatus($hash); + } +return; +} + + +1; + +__END__ + +=pod + +=encoding utf8 +=item device +=item summary Klafs Sauna control +=item summary_DE Klafs Saunasteuerung +=begin html + + +

          Klafs Sauna control

          +
            + The module receives data and sends commands to the Klafs app.
            + In the current version, the sauna can be turned on and off, and the parameters can be set. +
            +
            + Requirements +
              +
              + The SaunaID must be known. This can be found in the URL directly after logging in to the app (http://sauna-app.klafs.com).
              + The ID is there with the parameter ?s=xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx.
              + In addition, the user name and password must be known, as well as the PIN that was defined on the sauna module. +
            +
            + + Definition and use +
              +
              + The module is defined without mandatory parameters.
              + User name, password, refresh interval, saunaID and pin defined on the sauna module are set as attributes.
              +
            +
              + Definition of the module +
              +
            +
              +
              + define <name> Klafs <Intervall>
              + attr <name> <saunaid> <xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx>
              + attr <name> <username> <xxxxxx>
              + attr <name> <pin> <1234>
              + attr <name> <interval> <60>
              +
              + set <name> <password> <secret>
              +
            +
          +
            + Example of a module definition:
            +
              +
              + define mySauna Klafs
              + attr mySauna saunaid ab0c123d-ef4g-5h67-8ij9-k0l12mn34op5
              + attr mySauna username user01
              + attr mySauna pin 1234
              + attr mySauna interval 60
              +
              + set mySauna password secret
              +
            + + Set +
            +
              + + + + + + + + + + + + + + + + + + + + + +
              ResetLoginFailuresIf the login fails, the Reading LoginFailures is set to 1. This locks the automatic login from this module.
              + Klafs locks the account after 3 failed attempts. So that not automatically 3 wrong logins are made in a row.
              + ResetLoginFailures resets the reading to 0. Before this, you should have successfully logged in to the app or sauna-app.klafs.com
              + or reset the password. Successful login resets the number of failed attempts in the Klafs cloud. +
              offTurns off the sauna|sanarium|infrared - without parameters.
              on + set <name> on without parameters - default sauna 90 degrees
              + set <name> on Sauna 90 - 3 parameters possible: "Sauna" with temperature [10-100]; Optional time [19:30].
              + set <name> on Saunarium 65 5 - 4 parameters possible: "Sanarium" with temperature [40-75]; Optional HumidtyLevel [0-10] and time [19:30].
              + set <name> on Infrared 30 5 - 4 parameters possible: "Infrared" with temperature [20-40] and IR Level [0-10]; Optional time [19:30].
              + Infrared works, but is not supported because no test environment is available. +
              UpdateRefreshes the readings and performs a login if necessary.
              +
            +
            + Get +
            +
              + + + + + + + + + + + + + + +
              SaunaIDReads out the available SaunaIDs.
              helpDisplays the help for the SET commands.
              +
            +
            + + Readings +
              +
              + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
              Mode Sauna, Sanarium or Infrared
              LoginFailuresFailed login attempts to the app. If the value is set to 1, no login attempts are made by the module. See set <name> ResetLoginFailures
              RestzeitRemaining bathing time. Value from bathingHours and bathingMinutes
              antiforgery_date Date of the antiforgery cookie. This is generated when the program is switched on.
              bathingHours Hour of remaining bath time
              bathingMinutesMinute of remaining bath time
              cookieExpireLogincookie runtime. 2 days
              currentHumidityIn sanarium mode. Percentage humidity
              currentHumidityStatusundefined reading
              currentTemperatureTemperature in the sauna. 0 When the sauna is off
              currentTemperatureStatusundefined reading
              firstnameDefined first name in the app
              irSelectedtrue/false - Currently set operating mode Infrared
              isConnectedtrue/false - Sauna connected to the app
              isPoweredOntrue/false - Sauna is on/off
              langcloudLanguage set in the app
              last_errormsgLast error message. Often that the safety check door contact was not performed.
              + Safety check must be performed with the reed contact on the door +
              lastnameDefined last name in the app
              mailDefined mail address in the app
              sanariumSelectedtrue/false - Currently set operating mode Sanarium
              saunaIdSaunaID defined as an attribute
              saunaSelectedtrue/false - Currently set operating mode Sauna
              selectedHourDefined switch-on time. Here hour
              selectedHumLevelDefined humidity levels in sanarium operation
              selectedIrLevelDefined intensity in infrared mode
              selectedIrTemperatureDefined infrotemperature
              selectedMinuteDefined switch-on time. Here minute
              selectedSanariumTemperatureDefined sanarium temperature
              selectedSaunaTemperatureDefined sauna temperature
              showBathingHourtrue/false - not further defined. true, if sauna is on.
              standbytimeDefined standby time in the app.
              poweron/off
              statusCodeundefined reading
              statusMessageundefined reading
              usernameUsername defined as an attribute
              +
              +
            +
          +=end html + +=begin html_DE + + +

          Klafs Saunasteuerung

          +
            + Das Modul empfängt Daten und sendet Befehle an die Klafs App.
            + In der aktuellen Version kann die Sauna an- bzw. ausgeschaltet werden und dabei die Parameter mitgegeben werden. +
            +
            + Voraussetzungen +
              +
              + Die SaunaID muss bekannt sein. Diese findet sich in der URL direkt nach dem Login an der App (http://sauna-app.klafs.com).
              + Dort steht die ID mit dem Parameter ?s=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
              + Darüberhinaus müssen Benutzername und Passwort bekannt sein sowie die PIN, die am Saunamodul definiert wurde. +
            +
            + + Definition und Verwendung +
              +
              + Das Modul wird ohne Pflichtparameter definiert.
              + Benutzername, Passwort, Refresh-Intervall, SaunaID, und am Saunamodul definierte Pin werden als Attribute gesetzt.
              +
            +
              + Definition des Moduls +
              +
            +
              +
              + define <name> Klafs <Intervall>
              + attr <name> <saunaid> <xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx>
              + attr <name> <username> <xxxxxx>
              + attr <name> <pin> <1234>
              + attr <name> <interval> <60>
              +
              + set <name> <password> <xxxxxx>
              +
            +
          +
            + Beispiel für eine Moduldefinition:
            +
              +
              + define mySauna Klafs
              + attr mySauna saunaid ab0c123d-ef4g-5h67-8ij9-k0l12mn34op5
              + attr mySauna username user01
              + attr mySauna pin 1234
              + attr mySauna interval 60
              +
              + set mySauna password geheim
              +
            + + Set +
            +
              + + + + + + + + + + + + + + + + + + + + + +
              ResetLoginFailuresBei 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 zurückgesetzt haben. Erfolgreicher Login resetet die Anzahl der Fehlversuche in der Klafs-Cloud. +
              offSchaltet die Sauna|Sanarium|Infrared aus - ohne Parameter.
              on + set <name> on ohne Parameter - Default Sauna 90 Grad
              + set <name> on Sauna 90 - 3 Parameter möglich: "Sauna" mit Temperatur [10-100]; Optional Uhrzeit [19:30]
              + set <name> on Saunarium 65 5 - 4 Parameter möglich: "Sanarium" mit Temperatur [40-75]; Optional HumidtyLevel [0-10] und Uhrzeit [19:30]
              + set <name> on Infrared 30 5 - 4 Parameter möglich: "Infrarot" mit Temperatur [20-40] und IR Level [0-10]; Optional Uhrzeit [19:30]
              + Infrarot funktioniert, ist aber nicht supported, da keine Testumgebung verfügbar. +
              UpdateRefresht die Readings und führt ggf. ein Login durch.
              +
            +
            + Get +
            +
              + + + + + + + + + + + + + + +
              SaunaIDLiest die verfügbaren SaunaIDs aus.
              helpZeigt die Hilfe für die SET Befehle an.
              +
            +
            + + Readings +
              +
              + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
              Mode Sauna, Sanarium oder Infrared
              LoginFailuresFehlerhafte Loginversuche an der App. Steht der Wert auf 1, werden vom Modul keine Loginversuche unternommen. Siehe set <name> ResetLoginFailures
              RestzeitRestliche Badezeit. Wert aus bathingHours und bathingMinutes
              antiforgery_date Datum des Antiforgery Cookies. Dieses wird beim Einschalten erzeugt.
              bathingHours Stunde der Restbadezeit
              bathingMinutesMinute der Restbadezeit
              cookieExpireLaufzeit des Logincookies. 2 Tage
              currentHumidityIm Sanariumbetrieb. Prozentuale Luftfeuchtigkeit
              currentHumidityStatusnicht definiertes Reading
              currentTemperatureTemperatur in der Sauna. 0 wenn die Sauna aus ist
              currentTemperatureStatusnicht definiertes Reading
              firstnameDefinierter Vorname in der App
              irSelectedtrue/false - Aktuell eingestellter Betriebsmodus Infrarot
              isConnectedtrue/false - Sauna mit der App verbunden
              isPoweredOntrue/false - Sauna ist an/aus
              langcloudEingestellte Sprache in der App
              last_errormsgLetzte Fehlermeldung. Häufig, dass die Sicherheitsüberprüfung Türkontakt nicht durchgeführt wurde.
              + Sicherheitsüberprüfung muss durchgeführt werden mit dem Reedkontakt an der Tür. +
              lastnameDefinierter Nachname in der App
              mailDefinierte Mailadresse in der App
              sanariumSelectedtrue/false - Aktuell eingestellter Betriebsmodus Sanarium
              saunaIdSaunaID, die als Attribut definiert wurde
              saunaSelectedtrue/false - Aktuell eingestellter Betriebsmodus Sauna
              selectedHourDefinierte Einschaltzeit. Hier Stunde
              selectedHumLevelDefinierte Luftfeuchtigkeitslevel im Sanariumbetrieb
              selectedIrLevelDefinierte Intensivität im Infrarotbetrieb
              selectedIrTemperatureDefinierte Infrottemperatur
              selectedMinuteDefinierte Einschaltzeit. Hier Minute
              selectedSanariumTemperatureDefinierte Sanariumtemperatur
              selectedSaunaTemperatureDefinierte Saunatemperatur
              showBathingHourtrue/false - nicht näher definiert. true, wenn Sauna an ist.
              standbytimeDefinierte Standbyzeit in der App.
              poweron/off
              statusCodenicht definiertes Reading
              statusMessagenicht definiertes Reading
              usernameBenutzername, der als Attribut definiert wurde
              +
              +
            +
          +=end html_DE +=cut diff --git a/70_Klafs.pm b/70_Klafs.pm index b65e75d..7115f30 100644 --- a/70_Klafs.pm +++ b/70_Klafs.pm @@ -69,6 +69,7 @@ BEGIN { ReadingsVal HttpUtils_NonblockingGet HttpUtils_BlockingGet + readingsDelete )) }; @@ -352,50 +353,56 @@ sub klafs_getStatus{ 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: 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", + method => "GET", 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, -## }); + #Name Vorname Mail Benutzername + #GET Anfrage mit ASPXAUTH + my $header_user = "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"; + + + 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, -## }); + my $header_set = "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"; + + HttpUtils_NonblockingGet({ + url => "https://sauna-app-19.klafs.com/SaunaApp/ChangeSettings", + timeout => 5, + hash => $hash, + method => "GET", + header => $header_set, + callback => \&Klafs_GETSettings, + }); return; } @@ -453,6 +460,8 @@ sub klafs_getStatusResponse { readingsBulkUpdate($hash, $key, $new); } } + ## unset ErrorMessageHeader. Dort steht "Fehler" drin. Wert wird mitgeliefert auch wenn ErrorMessage leer ist. Bei Fehler wird Reading last_errormsg gesetzt + readingsDelete($hash, "ErrorMessageHeader"); Klafs_CONNECTED($hash,'connected'); readingsEndUpdate($hash, 1); @@ -478,8 +487,8 @@ sub Klafs_GETProfile { # 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=~/ Default Sauna 90 Grad | +|on | ohne Parameter -> Starten mit zuletzt verwendeten Werten | | | 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] | @@ -629,7 +638,7 @@ sub Klafs_GetSaunaIDs_Send{ my ($name,$self) = ($hash->{NAME},Klafs_Whoami()); my $aspxauth = $hash->{Klafs}->{cookie}; return if $hash->{Klafs}->{LoginFailures} > 0; - Log3 ($name, 5, "$name ($self) - executed1."); + Log3 ($name, 5, "$name ($self) - GetSauna ID start."); 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". @@ -653,7 +662,7 @@ sub Klafs_GetSaunaIDs_Receive { my $returnwert1; my $returnwert2; - Log3 ($name, 5, "$name ($self) - executed2."); + Log3 ($name, 5, "$name ($self) - GetSauna ID Ende."); if ($err ne "") { Log3 ($name, 4, "$name ($self) - error."); @@ -711,7 +720,7 @@ sub Set { my $FIFTEEN_MINS = (15 * 60); my $now = time; if (my $diff = $now % $FIFTEEN_MINS) { - $now += $FIFTEEN_MINS - $diff; + $now += $FIFTEEN_MINS - $diff; } my $next = scalar localtime $now; # doppelte Leerzeichen bei einstelligen Datumsangaben entfernen @@ -728,16 +737,16 @@ sub Set { #$VAR4 = '15:15:00'; #$VAR5 = '2022'; - if($std < 10){ - if(substr($std,0,1) eq "0"){ - $std = substr($std,1,1); - } - } - if($min < 10){ - if(substr($min,0,1) eq "0"){ - $min = substr($min,1,1); - } - } +## if($std < 10){ +## if(substr($std,0,1) eq "0"){ +## $std = substr($std,1,1); +## } +## } +## if($min < 10){ +## if(substr($min,0,1) eq "0"){ +## $min = substr($min,1,1); +## } +## } # on () @@ -745,34 +754,43 @@ sub Set { Log3 ($name, 2, "Klafs set $name " . $cmd); klafs_getStatus($hash); - my $mode = shift @args; + my $mode = ""; + $mode = shift @args; + my $aspxauth = $hash->{Klafs}->{cookie}; my $pin = $hash->{Klafs}->{pin}; my $saunaid = $hash->{Klafs}->{saunaid}; - my $selectedSaunaTemperature = ReadingsVal( $name, "selectedSaunaTemperature", "90" ); - my $selectedSanariumTemperature = ReadingsVal( $name, "selectedSanariumTemperature", "65" ); - my $selectedIrTemperature = ReadingsVal( $name, "selectedIrTemperature", "0" ); - my $selectedHumLevel = ReadingsVal( $name, "selectedHumLevel", "5" ); - my $selectedIrLevel = ReadingsVal( $name, "selectedIrLevel", "0" ); - my $isConnected = ReadingsVal( $name, "isConnected", "true" ); - my $isPoweredOn = ReadingsVal( $name, "isPoweredOn", "false" ); - my $isReadyForUse = ReadingsVal( $name, "isReadyForUse", "false" ); - my $currentTemperature = ReadingsVal( $name, "currentTemperature", "141" ); - if($currentTemperature eq "0"){ - $currentTemperature = "141"; - } - my $currentHumidity = ReadingsVal( $name, "currentHumidity", "0" ); - my $statusCode = ReadingsVal( $name, "statusCode", "0" ); - my $statusMessage = ReadingsVal( $name, "statusMessage", "" ); - if($statusMessage eq ""){ - $statusMessage = 'null'; - } - my $showBathingHour = ReadingsVal( $name, "showBathingHour", "false" ); - my $bathingHours = ReadingsVal( $name, "bathingHours", "0" ); - my $bathingMinutes = ReadingsVal( $name, "bathingMinutes", "0" ); - my $currentHumidityStatus = ReadingsVal( $name, "currentHumidityStatus", "0" ); - my $currentTemperatureStatus = ReadingsVal( $name, "currentTemperatureStatus", "0" ); + + my $header_on = "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: application/json, text/javascript, */*; q=0.01\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"; +# my $selectedSaunaTemperature = ReadingsVal( $name, "selectedSaunaTemperature", "90" ); +# my $selectedSanariumTemperature = ReadingsVal( $name, "selectedSanariumTemperature", "65" ); +# my $selectedIrTemperature = ReadingsVal( $name, "selectedIrTemperature", "0" ); +# my $selectedHumLevel = ReadingsVal( $name, "selectedHumLevel", "5" ); +# my $selectedIrLevel = ReadingsVal( $name, "selectedIrLevel", "0" ); +# my $isConnected = ReadingsVal( $name, "isConnected", "true" ); +# my $isPoweredOn = ReadingsVal( $name, "isPoweredOn", "false" ); +# my $isReadyForUse = ReadingsVal( $name, "isReadyForUse", "false" ); +# my $currentTemperature = ReadingsVal( $name, "currentTemperature", "141" ); +# if($currentTemperature eq "0"){ +# $currentTemperature = "141"; +# } +# my $currentHumidity = ReadingsVal( $name, "currentHumidity", "0" ); +# my $statusCode = ReadingsVal( $name, "statusCode", "0" ); +# my $statusMessage = ReadingsVal( $name, "statusMessage", "" ); +# if($statusMessage eq ""){ +# $statusMessage = 'null'; +# } +# my $showBathingHour = ReadingsVal( $name, "showBathingHour", "false" ); +# my $bathingHours = ReadingsVal( $name, "bathingHours", "0" ); +# my $bathingMinutes = ReadingsVal( $name, "bathingMinutes", "0" ); +# my $currentHumidityStatus = ReadingsVal( $name, "currentHumidityStatus", "0" ); +# my $currentTemperatureStatus = ReadingsVal( $name, "currentTemperatureStatus", "0" ); if ( $pin eq "") { my $msg = "Missing attribute: attr $name pin "; @@ -784,7 +802,24 @@ sub Set { return $msg; }else{ my $datauser_cv = ""; + if ( $mode eq "Sauna"){ + + # Sauna Modus wechseln + my $datauser_mode = '{"id":"'.$saunaid.'","selected_mode":1}'; + Log3 ($name, 4, "$name - JSON_MODE: $datauser_mode"); + + HttpUtils_BlockingGet({ + url => "https://sauna-app-19.klafs.com/SaunaApp/SetMode", + timeout => 5, + hash => $hash, + method => "POST", + header => $header_on, + data => $datauser_mode, + }); + + + # Sauna hat 1 Parameter: Temperatur #return "Zu wenig Argumente: Temperatur fehlt" if ( @args < 1 ); my $temperature = shift @args; @@ -801,6 +836,22 @@ sub Set { $temperature = 90; } } + + # Sauna Temperatur wechseln + my $datauser_temp = '{"id":"'.$saunaid.'","temperature":"'.$temperature.'"}'; + Log3 ($name, 4, "$name - JSON_TEMP: $datauser_temp"); + + HttpUtils_BlockingGet({ + url => "https://sauna-app-19.klafs.com/SaunaApp/ChangeTemperature", + timeout => 5, + hash => $hash, + method => "POST", + header => $header_on, + data => $datauser_temp, + }); + + + my $Time; $Time = shift @args; @@ -827,8 +878,35 @@ sub Set { if ($std <0 || $std >23 || $min <0 || $min >59){ return "Checken Sie das Zeitformat $std:$min\n"; } - $datauser_cv = '{"changedData":{"saunaId":"'.$saunaid.'","saunaSelected":true,"sanariumSelected":false,"irSelected":false,"selectedSaunaTemperature":'.$temperature.',"selectedSanariumTemperature":'.$selectedSanariumTemperature.',"selectedIrTemperature":'.$selectedIrTemperature.',"selectedHumLevel":'.$selectedHumLevel.',"selectedIrLevel":'.$selectedIrLevel.',"selectedHour":'.$std.',"selectedMinute":'.$min.',"isConnected":'.$isConnected.',"isPoweredOn":'.$isPoweredOn.',"isReadyForUse":'.$isReadyForUse.',"currentTemperature":'.$currentTemperature.',"currentHumidity":'.$currentHumidity.',"statusCode":'.$statusCode.',"statusMessage":'.$statusMessage.',"showBathingHour":'.$showBathingHour.',"bathingHours":'.$bathingHours.',"bathingMinutes":'.$bathingMinutes.',"currentHumidityStatus":'.$currentHumidityStatus.',"currentTemperatureStatus":'.$currentTemperatureStatus.'}}'; + # Sauna Zeit wechseln + my $datauser_zeit = '{"id":"'.$saunaid.'","time_set":true,"hours":'.$std.',"minutes":'.$min.'}'; + Log3 ($name, 4, "$name - JSON_ZEIT: $datauser_zeit"); + + HttpUtils_BlockingGet({ + url => "https://sauna-app-19.klafs.com/SaunaApp/SetSelectedTime", + timeout => 5, + hash => $hash, + method => "POST", + header => $header_on, + data => $datauser_zeit, + }); + + }elsif ( $mode eq "Sanarium" ) { + + # Sanarium Modus wechseln + my $datauser_mode = '{"id":"'.$saunaid.'","selected_mode":2}'; + Log3 ($name, 4, "$name - JSON_MODE: $datauser_mode"); + + HttpUtils_BlockingGet({ + url => "https://sauna-app-19.klafs.com/SaunaApp/SetMode", + timeout => 5, + hash => $hash, + method => "POST", + header => $header_on, + data => $datauser_mode, + }); + my $temperature = shift @args; @@ -843,8 +921,21 @@ sub Set { if ($temperature eq "" || $temperature eq 0){ $temperature = 65; } - } + + # Sanarium Temperatur wechseln + my $datauser_temp = '{"id":"'.$saunaid.'","temperature":"'.$temperature.'"}'; + Log3 ($name, 4, "$name - JSON_TEMP: $datauser_temp"); + + HttpUtils_BlockingGet({ + url => "https://sauna-app-19.klafs.com/SaunaApp/ChangeTemperature", + timeout => 5, + hash => $hash, + method => "POST", + header => $header_on, + data => $datauser_temp, + }); + my $Time; my $level; $level = shift @args; @@ -890,6 +981,18 @@ sub Set { if ($std <0 || $std >23 || $min <0 || $min >59){ return "Checken Sie das Zeitformat $std:$min\n"; } + # Sanarium Zeit wechseln + my $datauser_zeit = '{"id":"'.$saunaid.'","time_set":true,"hours":'.$std.',"minutes":'.$min.'}'; + Log3 ($name, 4, "$name - JSON_ZEIT: $datauser_zeit"); + + HttpUtils_BlockingGet({ + url => "https://sauna-app-19.klafs.com/SaunaApp/SetSelectedTime", + timeout => 5, + hash => $hash, + method => "POST", + header => $header_on, + data => $datauser_zeit, + }); # Auf volle 10 Minuten runden #if( substr($min,-1,1) > 0){ @@ -912,178 +1015,263 @@ sub Set { if ($level eq ""){ $level = 5; } - - } - $datauser_cv = '{"changedData":{"saunaId":"'.$saunaid.'","saunaSelected":false,"sanariumSelected":true,"irSelected":false,"selectedSaunaTemperature":'.$selectedSaunaTemperature.',"selectedSanariumTemperature":'.$temperature.',"selectedIrTemperature":'.$selectedIrTemperature.',"selectedHumLevel":'.$level.',"selectedIrLevel":'.$selectedIrLevel.',"selectedHour":'.$std.',"selectedMinute":'.$min.',"isConnected":'.$isConnected.',"isPoweredOn":'.$isPoweredOn.',"isReadyForUse":'.$isReadyForUse.',"currentTemperature":'.$currentTemperature.',"currentHumidity":'.$currentHumidity.',"statusCode":'.$statusCode.',"statusMessage":'.$statusMessage.',"showBathingHour":'.$showBathingHour.',"bathingHours":'.$bathingHours.',"bathingMinutes":'.$bathingMinutes.',"currentHumidityStatus":'.$currentHumidityStatus.',"currentTemperatureStatus":'.$currentTemperatureStatus.'}}'; - }elsif ( $mode eq "Infrared" ) { - my $temperature = shift @args; - if(!looks_like_number($temperature)){ - return "Geben Sie einen nummerischen Wert fuer ein"; - } - if ($temperature >= 20 && $temperature <=40 && $temperature ne ""){ - $temperature = $temperature; - }else{ - # Letzer Wert oder Standardtemperatur - $temperature = ReadingsVal( $name, "selectedIrTemperature", "" ); - if ($temperature eq "" || $temperature eq 0){ - $temperature = 35; - } - } - my $Time; - my $level; - $level = shift @args; - $Time = shift @args; - - if(!defined($Time)){ - $Time ="$Uhrzeit[0]:$Uhrzeit[1]"; } + # Sanarium Feuchtigkeit wechseln + my $datauser_hlevel = '{"id":"'.$saunaid.'","level":"'.$level.'"}'; + Log3 ($name, 4, "$name - JSON_HUM_LEVEL: $datauser_hlevel"); - # Parameter level ist optional. Wird in der ersten Variable eine anstelle des Levels eine Uhrzeit gefunden, dann level auf "" setzen und $std,$min setzen - if($level =~ /:/ || $Time =~ /:/){ - if($level =~ /:/){ - my @Timer = split(/:/,$level); - $std = $Timer[0]; - $min = $Timer[1]; - if($std < 10){ - if(substr($std,0,1) eq "0"){ - $std = substr($std,1,1); - } - } - if($min < 10){ - if(substr($min,0,1) eq "0"){ - $min = substr($min,1,1); - } - } - $level = ""; - }else{ - my @Timer = split(/:/,$Time); - $std = $Timer[0]; - $min = $Timer[1]; - if($std < 10){ - if(substr($std,0,1) eq "0"){ - $std = substr($std,1,1); - } - } - if($min < 10){ - if(substr($min,0,1) eq "0"){ - $min = substr($min,1,1); - } - } - } - } - if ($std <0 || $std >23 || $min <0 || $min >59){ - return "Checken Sie das Zeitformat $std:$min\n"; - } + HttpUtils_BlockingGet({ + url => "https://sauna-app-19.klafs.com/SaunaApp/ChangeHumLevel", + timeout => 5, + hash => $hash, + method => "POST", + header => $header_on, + data => $datauser_hlevel, + }); - if ($level >= 0 && $level <=10 && $level ne "" ){ - $level = $level; - }else{ - # Letzer Wert oder Standardlevel - $level = ReadingsVal( $name, "selectedIrLevel", "" ); - if ($level eq ""){ - $level = 5; - } - } - $datauser_cv = '{"changedData":{"saunaId":"'.$saunaid.'","saunaSelected":false,"sanariumSelected":false,"irSelected":true,"selectedSaunaTemperature":'.$selectedSaunaTemperature.',"selectedSanariumTemperature":'.$selectedSanariumTemperature.',"selectedIrTemperature":'.$temperature.',"selectedHumLevel":'.$selectedHumLevel.',"selectedIrLevel":'.$level.',"selectedHour":'.$std.',"selectedMinute":'.$min.',"isConnected":'.$isConnected.',"isPoweredOn":'.$isPoweredOn.',"isReadyForUse":'.$isReadyForUse.',"currentTemperature":'.$currentTemperature.',"currentHumidity":'.$currentHumidity.',"statusCode":'.$statusCode.',"statusMessage":'.$statusMessage.',"showBathingHour":'.$showBathingHour.',"bathingHours":'.$bathingHours.',"bathingMinutes":'.$bathingMinutes.',"currentHumidityStatus":'.$currentHumidityStatus.',"currentTemperatureStatus":'.$currentTemperatureStatus.'}}'; - - }else{ - $datauser_cv = '{"changedData":{"saunaId":"'.$saunaid.'","saunaSelected":true,"sanariumSelected":false,"irSelected":false,"selectedSaunaTemperature":90,"selectedSanariumTemperature":'.$selectedSanariumTemperature.',"selectedIrTemperature":'.$selectedIrTemperature.',"selectedHumLevel":'.$selectedHumLevel.',"selectedIrLevel":'.$selectedIrLevel.',"selectedHour":'.$std.',"selectedMinute":'.$min.',"isConnected":'.$isConnected.',"isPoweredOn":'.$isPoweredOn.',"isReadyForUse":'.$isReadyForUse.',"currentTemperature":'.$currentTemperature.',"currentHumidity":'.$currentHumidity.',"statusCode":'.$statusCode.',"statusMessage":'.$statusMessage.',"showBathingHour":'.$showBathingHour.',"bathingHours":'.$bathingHours.',"bathingMinutes":'.$bathingMinutes.',"currentHumidityStatus":'.$currentHumidityStatus.',"currentTemperatureStatus":'.$currentTemperatureStatus.'}}'; } - - Log3 ($name, 4, "$name - JSON ON: $datauser_cv"); - # 1) Werte aendern - #print "Mode: ". $mode . " Temperature: ". $temperature . " Level: " .$level ."\n$datauser_cv\n\n"; - my $header_cv = "Content-Type: application/json\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". - "Cookie: $aspxauth"; - HttpUtils_BlockingGet({ - url => "https://sauna-app-19.klafs.com//Control/PostConfigChange", - timeout => 5, - hash => $hash, - method => "POST", - header => $header_cv, - data => $datauser_cv, - }); +## elsif ( $mode eq "Infrared" ) { +## +## # Sanarium Modus wechseln +## my $header = "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: application/json, text/javascript, */*; q=0.01\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"; +## +## my $datauser_mode = '{"id":"'.$saunaid.'","selected_mode":3}'; +## Log3 ($name, 4, "$name - JSON_MODE: $datauser_mode"); +## +## HttpUtils_BlockingGet({ +## url => "https://sauna-app-19.klafs.com/SaunaApp/SetMode", +## timeout => 5, +## hash => $hash, +## method => "POST", +## header => $header, +## data => $datauser_mode, +## }); +## +## +## my $temperature = shift @args; +## if(!looks_like_number($temperature)){ +## return "Geben Sie einen nummerischen Wert fuer ein"; +## } +## if ($temperature >= 20 && $temperature <=40 && $temperature ne ""){ +## $temperature = $temperature; +## }else{ +## # Letzer Wert oder Standardtemperatur +## $temperature = ReadingsVal( $name, "selectedIrTemperature", "" ); +## if ($temperature eq "" || $temperature eq 0){ +## $temperature = 35; +## } +## } +## +## +## +## my $Time; +## my $level; +## $level = shift @args; +## $Time = shift @args; +## +## if(!defined($Time)){ +## $Time ="$Uhrzeit[0]:$Uhrzeit[1]"; +## } +## +## # Parameter level ist optional. Wird in der ersten Variable eine anstelle des Levels eine Uhrzeit gefunden, dann level auf "" setzen und $std,$min setzen +## if($level =~ /:/ || $Time =~ /:/){ +## if($level =~ /:/){ +## my @Timer = split(/:/,$level); +## $std = $Timer[0]; +## $min = $Timer[1]; +## if($std < 10){ +## if(substr($std,0,1) eq "0"){ +## $std = substr($std,1,1); +## } +## } +## if($min < 10){ +## if(substr($min,0,1) eq "0"){ +## $min = substr($min,1,1); +## } +## } +## $level = ""; +## }else{ +## my @Timer = split(/:/,$Time); +## $std = $Timer[0]; +## $min = $Timer[1]; +## if($std < 10){ +## if(substr($std,0,1) eq "0"){ +## $std = substr($std,1,1); +## } +## } +## if($min < 10){ +## if(substr($min,0,1) eq "0"){ +## $min = substr($min,1,1); +## } +## } +## } +## } +## if ($std <0 || $std >23 || $min <0 || $min >59){ +## return "Checken Sie das Zeitformat $std:$min\n"; +## } +## +## if ($level >= 0 && $level <=10 && $level ne "" ){ +## $level = $level; +## }else{ +## # Letzer Wert oder Standardlevel +## $level = ReadingsVal( $name, "selectedIrLevel", "" ); +## if ($level eq ""){ +## $level = 5; +## } +## } +## $datauser_cv = '{"changedData":{"saunaId":"'.$saunaid.'","saunaSelected":false,"sanariumSelected":false,"irSelected":true,"selectedSaunaTemperature":'.$selectedSaunaTemperature.',"selectedSanariumTemperature":'.$selectedSanariumTemperature.',"selectedIrTemperature":'.$temperature.',"selectedHumLevel":'.$selectedHumLevel.',"selectedIrLevel":'.$level.',"selectedHour":'.$std.',"selectedMinute":'.$min.',"isConnected":'.$isConnected.',"isPoweredOn":'.$isPoweredOn.',"isReadyForUse":'.$isReadyForUse.',"currentTemperature":'.$currentTemperature.',"currentHumidity":'.$currentHumidity.',"statusCode":'.$statusCode.',"statusMessage":'.$statusMessage.',"showBathingHour":'.$showBathingHour.',"bathingHours":'.$bathingHours.',"bathingMinutes":'.$bathingMinutes.',"currentHumidityStatus":'.$currentHumidityStatus.',"currentTemperatureStatus":'.$currentTemperatureStatus.'}}'; +## +## }else{ +## $datauser_cv = '{"changedData":{"saunaId":"'.$saunaid.'","saunaSelected":true,"sanariumSelected":false,"irSelected":false,"selectedSaunaTemperature":90,"selectedSanariumTemperature":'.$selectedSanariumTemperature.',"selectedIrTemperature":'.$selectedIrTemperature.',"selectedHumLevel":'.$selectedHumLevel.',"selectedIrLevel":'.$selectedIrLevel.',"selectedHour":'.$std.',"selectedMinute":'.$min.',"isConnected":'.$isConnected.',"isPoweredOn":'.$isPoweredOn.',"isReadyForUse":'.$isReadyForUse.',"currentTemperature":'.$currentTemperature.',"currentHumidity":'.$currentHumidity.',"statusCode":'.$statusCode.',"statusMessage":'.$statusMessage.',"showBathingHour":'.$showBathingHour.',"bathingHours":'.$bathingHours.',"bathingMinutes":'.$bathingMinutes.',"currentHumidityStatus":'.$currentHumidityStatus.',"currentTemperatureStatus":'.$currentTemperatureStatus.'}}'; +## } +## +## Log3 ($name, 4, "$name - JSON ON: $datauser_cv"); +## # 1) Werte aendern +## #print "Mode: ". $mode . " Temperature: ". $temperature . " Level: " .$level ."\n$datauser_cv\n\n"; +## +## my $header_cv = "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: application/json, text/javascript, */*; q=0.01\r\n". +## "Accept-Encoding: gzip, deflate, br\r\n". +## "Accept-Language: de,en;q=0.7,en-US;q=0.3\r\n". +## "Cookie: $aspxauth"; +## HttpUtils_BlockingGet({ +## url => "https://sauna-app-19.klafs.com/SaunaApp/StartCabin", +## timeout => 5, +## hash => $hash, +## method => "POST", +## header => $header_cv, +## data => $datauser_cv, +## }); - my $state_onoff = ReadingsVal( $name, "isPoweredOn", "false" ); - + + my $state_onoff = ReadingsVal( $name, "power", "off" ); # Einschalten, wenn Sauna aus ist. - if($state_onoff eq "false"){ - my $header_af = "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"; - my $datauser_af = "s=$saunaid"; - # 2 Steps: 2) Antiforgery erzeugen; 3) Einschalten - HttpUtils_NonblockingGet({ - url => "https://sauna-app-19.klafs.com/Control/EnterPin", - timeout => 5, - hash => $hash, - method => "POST", - header => $header_af, - data => $datauser_af, - callback=>sub($$$){ - 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); - for my $cookie ($header =~ m/set-cookie: ?(.*)/gi) { - $cookie =~ /([^,; ]+)=([^,;\s\v]+)[;,\s\v]*([^\v]*)/; - my $antiforgery = $1 . "=" .$2 .";"; - my $antiforgery_date = strftime("%Y-%m-%d %H:%M:%S", localtime(time())); - readingsBulkUpdate( $hash, "antiforgery_date", "$antiforgery_date", 1 ); - Log3 ($name, 5, "$name: Antiforgery found: $antiforgery"); - $hash->{Klafs}->{antiforgery} = $antiforgery; - } - readingsEndUpdate($hash, 1); +Log3 ($name, 4, "$name - Hier1 : $state_onoff"); - # 2) Einschalten - my $headeron = "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"; - my $antiforgery = $hash->{Klafs}->{antiforgery}; - my $datauseron = "$antiforgery&Pin=$pin&saunaId=$saunaid"; - HttpUtils_NonblockingGet({ - url => "https://sauna-app-19.klafs.com/Control/EnterPin", - timeout => 5, - hash => $hash, - method => "POST", - header => $headeron, - data => $datauseron, - callback => sub($$$){ - my ($param, $err, $data) = @_; - my $hash = $param->{hash}; - my $name = $hash->{NAME}; - Log3 ($name, 5, "header: $header"); - Log3 ($name, 5, "Data: $data"); - Log3 ($name, 5, "Error: $err"); - if($data=~/
          • /) { - readingsBeginUpdate ($hash); - 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"); - readingsBulkUpdate( $hash, "last_errormsg", "$err", 1 ); - } - readingsEndUpdate($hash, 1); - }else{ - $power = "on"; - Log3 ($name, 3, "Sauna on"); - readingsBeginUpdate ($hash); - readingsBulkUpdate( $hash, "power", $power, 1 ); - readingsBulkUpdate( $hash, "last_errormsg", "0", 1 ); - readingsEndUpdate($hash, 1); - klafs_getStatus($hash); - } - } - }); - } - }); - } - } + if($state_onoff eq "off"){ + # Einschalten + my $datauser_start = '{"id":"'.$saunaid.'","pin":"'.$pin.'","time_selected":false,"sel_hour":'.$std.',"sel_min":'.$min.'}'; +Log3 ($name, 4, "$name - Hier2 : $datauser_start"); + HttpUtils_NonblockingGet({ + url => "https://sauna-app-19.klafs.com/SaunaApp/StartCabin", + timeout => 5, + hash => $hash, + method => "POST", + header => $header_on, + data => $datauser_start, + callback => sub($$$){ + my ($param, $err, $data) = @_; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + my $header = $param->{httpheader}; + Log3 ($name, 4, "header: $header"); + Log3 ($name, 4, "Data: $data"); + Log3 ($name, 4, "Error: $err"); + if($data=~/"Success":false/) { + readingsBeginUpdate ($hash); + for my $err ($data =~ m /ErrorMessage":"?(.*)"/) { + 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"); + readingsBulkUpdate( $hash, "last_errormsg", "$err", 1 ); + } + readingsEndUpdate($hash, 1); + }else{ + $power = "on"; + Log3 ($name, 3, "Sauna on"); + readingsBeginUpdate ($hash); + readingsBulkUpdate( $hash, "power", $power, 1 ); + readingsBulkUpdate( $hash, "last_errormsg", "0", 1 ); + readingsEndUpdate($hash, 1); + klafs_getStatus($hash); + } + } + }); + + + +## my $header_af = "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"; +## my $datauser_af = "s=$saunaid"; +## # 2 Steps: 2) Antiforgery erzeugen; 3) Einschalten +## HttpUtils_NonblockingGet({ +## url => "https://sauna-app-19.klafs.com/Control/EnterPin", +## timeout => 5, +## hash => $hash, +## method => "POST", +## header => $header_af, +## data => $datauser_af, +## callback=>sub($$$){ +## 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); +## for my $cookie ($header =~ m/set-cookie: ?(.*)/gi) { +## $cookie =~ /([^,; ]+)=([^,;\s\v]+)[;,\s\v]*([^\v]*)/; +## my $antiforgery = $1 . "=" .$2 .";"; +## my $antiforgery_date = strftime("%Y-%m-%d %H:%M:%S", localtime(time())); +## readingsBulkUpdate( $hash, "antiforgery_date", "$antiforgery_date", 1 ); +## Log3 ($name, 5, "$name: Antiforgery found: $antiforgery"); +## $hash->{Klafs}->{antiforgery} = $antiforgery; +## } +## readingsEndUpdate($hash, 1); +## +## # 2) Einschalten +## my $headeron = "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"; +## my $antiforgery = $hash->{Klafs}->{antiforgery}; +## my $datauseron = "$antiforgery&Pin=$pin&saunaId=$saunaid"; +## HttpUtils_NonblockingGet({ +## url => "https://sauna-app-19.klafs.com/Control/EnterPin", +## timeout => 5, +## hash => $hash, +## method => "POST", +## header => $headeron, +## data => $datauseron, +## callback => sub($$$){ +## my ($param, $err, $data) = @_; +## my $hash = $param->{hash}; +## my $name = $hash->{NAME}; +## Log3 ($name, 5, "header: $header"); +## Log3 ($name, 5, "Data: $data"); +## Log3 ($name, 5, "Error: $err"); +## if($data=~/
              • /) { +## readingsBeginUpdate ($hash); +## 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"); +## readingsBulkUpdate( $hash, "last_errormsg", "$err", 1 ); +## } +## readingsEndUpdate($hash, 1); +## }else{ +## $power = "on"; +## Log3 ($name, 3, "Sauna on"); +## readingsBeginUpdate ($hash); +## readingsBulkUpdate( $hash, "power", $power, 1 ); +## readingsBulkUpdate( $hash, "last_errormsg", "0", 1 ); +## readingsEndUpdate($hash, 1); +## klafs_getStatus($hash); +## } +## } +## }); +## } +## }); + } ## Ende Wenn Sauna aus ist + } ## Ende PIN / SAUNAID vorhanden # sauna off }elsif ( $cmd eq "off" ) { @@ -1091,54 +1279,26 @@ sub Set { klafs_getStatus($hash); my $aspxauth = $hash->{Klafs}->{cookie}; - my $saunaid = $hash->{Klafs}->{saunaid}; - my $saunaSelected = ReadingsVal( $name, "saunaSelected", "true" ); - my $sanariumSelected = ReadingsVal( $name, "sanariumSelected", "false" ); - my $irSelected = ReadingsVal( $name, "irSelected", "false" ); - - my $selectedSaunaTemperature = ReadingsVal( $name, "selectedSaunaTemperature", "90" ); - my $selectedSanariumTemperature = ReadingsVal( $name, "selectedSanariumTemperature", "65" ); - my $selectedIrTemperature = ReadingsVal( $name, "selectedIrTemperature", "0" ); - my $selectedHumLevel = ReadingsVal( $name, "selectedHumLevel", "5" ); - my $selectedIrLevel = ReadingsVal( $name, "selectedIrLevel", "0" ); - my $selectedHour = ReadingsVal( $name, "selectedHour", "0" ); - my $selectedMinute = ReadingsVal( $name, "selectedMinute", "0" ); - - my $isConnected = ReadingsVal( $name, "isConnected", "true" ); - my $isPoweredOn = ReadingsVal( $name, "isPoweredOn", "false" ); - my $isReadyForUse = ReadingsVal( $name, "isReadyForUse", "false" ); - my $currentTemperature = ReadingsVal( $name, "currentTemperature", "141" ); - if($currentTemperature eq "0"){ - $currentTemperature = "141"; - } - my $currentHumidity = ReadingsVal( $name, "currentHumidity", "0" ); - my $statusCode = ReadingsVal( $name, "statusCode", "0" ); - my $statusMessage = ReadingsVal( $name, "statusMessage", "" ); - if($statusMessage eq ""){ - $statusMessage = 'null'; - } - my $showBathingHour = ReadingsVal( $name, "showBathingHour", "false" ); - my $bathingHours = ReadingsVal( $name, "bathingHours", "0" ); - my $bathingMinutes = ReadingsVal( $name, "bathingMinutes", "0" ); - my $currentHumidityStatus = ReadingsVal( $name, "currentHumidityStatus", "0" ); - my $currentTemperatureStatus = ReadingsVal( $name, "currentTemperatureStatus", "0" ); - + if ($saunaid eq ""){ my $msg = "Missing attribute: attr $name saunaid "; Log3 ($name, 1, $msg); return $msg; }else{ - my $header = "Content-Type: application/json\r\n". + my $header = "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: application/json, text/javascript, */*; q=0.01\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"; - my $datauser_end = '{"changedData":{"saunaId":"'.$saunaid.'","saunaSelected":'.$saunaSelected.',"sanariumSelected":'.$sanariumSelected.',"irSelected":'.$irSelected.',"selectedSaunaTemperature":'.$selectedSaunaTemperature.',"selectedSanariumTemperature":'.$selectedSanariumTemperature.',"selectedIrTemperature":'.$selectedIrTemperature.',"selectedHumLevel":'.$selectedHumLevel.',"selectedIrLevel":'.$selectedIrLevel.',"selectedHour":'.$selectedHour.',"selectedMinute":'.$selectedMinute.',"isConnected":'.$isConnected.',"isPoweredOn":'.$isPoweredOn.',"isReadyForUse":'.$isReadyForUse.',"currentTemperature":'.$currentTemperature.',"currentHumidity":'.$currentHumidity.',"statusCode":'.$statusCode.',"statusMessage":'.$statusMessage.',"showBathingHour":'.$showBathingHour.',"bathingHours":'.$bathingHours.',"bathingMinutes":'.$bathingMinutes.',"currentHumidityStatus":'.$currentHumidityStatus.',"currentTemperatureStatus":'.$currentTemperatureStatus.'}}'; + my $datauser_end = '{"id":"'.$saunaid.'"}'; Log3 ($name, 4, "$name - JSON_OFF: $datauser_end"); HttpUtils_BlockingGet({ - url => "https://sauna-app-19.klafs.com/Control/PostPowerOff", + url => "https://sauna-app-19.klafs.com/SaunaApp/StopCabin", timeout => 5, hash => $hash, method => "POST", @@ -1146,14 +1306,6 @@ sub Set { data => $datauser_end, }); - HttpUtils_BlockingGet({ - url => "https://sauna-app-19.klafs.com//Control/PostConfigChange", - timeout => 5, - hash => $hash, - method => "POST", - header => $header, - data => $datauser_end, - }); $power = "off"; readingsBeginUpdate ($hash); readingsBulkUpdate( $hash, "power", $power, 1 ); @@ -1316,7 +1468,7 @@ __END__ on - set <name> on without parameters - default sauna 90 degrees
                  + set <name> on without parameters - start with last used values
                  set <name> on Sauna 90 - 3 parameters possible: "Sauna" with temperature [10-100]; Optional time [19:30].
                  set <name> on Saunarium 65 5 - 4 parameters possible: "Sanarium" with temperature [40-75]; Optional HumidtyLevel [0-10] and time [19:30].
                  set <name> on Infrared 30 5 - 4 parameters possible: "Infrared" with temperature [20-40] and IR Level [0-10]; Optional time [19:30].
                  @@ -1582,7 +1734,7 @@ __END__ on - set <name> on ohne Parameter - Default Sauna 90 Grad
                  + set <name> on ohne Parameter - Starten mit zuletzt verwendeten Werten
                  set <name> on Sauna 90 - 3 Parameter möglich: "Sauna" mit Temperatur [10-100]; Optional Uhrzeit [19:30]
                  set <name> on Saunarium 65 5 - 4 Parameter möglich: "Sanarium" mit Temperatur [40-75]; Optional HumidtyLevel [0-10] und Uhrzeit [19:30]
                  set <name> on Infrared 30 5 - 4 Parameter möglich: "Infrarot" mit Temperatur [20-40] und IR Level [0-10]; Optional Uhrzeit [19:30]
                  diff --git a/Daten_Klafs.txt b/Daten_Klafs.txt new file mode 100644 index 0000000..09dd353 --- /dev/null +++ b/Daten_Klafs.txt @@ -0,0 +1,89 @@ +https://sauna-app-19.klafs.com/SaunaApp/GetData?id=ef0d004f-ae4b-4e91-8ae6-e8b72fc61df6 + +Temperatur ändern: +https://sauna-app-19.klafs.com/SaunaApp/ChangeTemperature +POST + Accept: application/json, text/javascript, */*; q=0.01 + Accept-Encoding: gzip, deflate, br + Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7 + Content-Type: application/json; charset=UTF-8 + +Daten +{"id":"ef0d004f-ae4b-4e91-8ae6-e8b72fc61df6","temperature":"94"} + + + + +Luftfeuchtigkeit ändern +https://sauna-app-19.klafs.com/SaunaApp/ChangeHumLevel +POST + Accept: application/json, text/javascript, */*; q=0.01 + Accept-Encoding: gzip, deflate, br + Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7 + Content-Type: application/json; charset=UTF-8 + + Daten + {"id":"ef0d004f-ae4b-4e91-8ae6-e8b72fc61df6","level":"5"} + + + +# Wechsel Sanarium / Sauna +https://sauna-app-19.klafs.com/SaunaApp/SetMode + POST + Accept: application/json, text/javascript, */*; q=0.01 + Accept-Encoding: gzip, deflate, br + Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7 + Content-Type: application/json; charset=UTF-8 + + Daten + {"id":"ef0d004f-ae4b-4e91-8ae6-e8b72fc61df6","selected_mode":2} + 1: Sauna + 2: Sanarium + 3: Infrarot + + + # Uhrzeit ändern + https://sauna-app-19.klafs.com/SaunaApp/SetSelectedTime + POST + Accept: application/json, text/javascript, */*; q=0.01 + Accept-Encoding: gzip, deflate, br + Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7 + Content-Type: application/json; charset=UTF-8 + + Daten + {"id":"ef0d004f-ae4b-4e91-8ae6-e8b72fc61df6","time_set":true,"hours":14,"minutes":55} + + +# Einschalten +https://sauna-app-19.klafs.com/SaunaApp/StartCabin +POST + Accept: application/json, text/javascript, */*; q=0.01 + Accept-Encoding: gzip, deflate, br + Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7 + Content-Type: application/json; charset=UTF-8 + + Daten + {"id":"ef0d004f-ae4b-4e91-8ae6-e8b72fc61df6","pin":"6677","time_selected":false,"sel_hour":9,"sel_min":30} + + +# Ausschalten +https://sauna-app-19.klafs.com/SaunaApp/StopCabin +POST + Accept: application/json, text/javascript, */*; q=0.01 + Accept-Encoding: gzip, deflate, br + Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7 + Content-Type: application/json; charset=UTF-8 + + + Daten + {"id":"ef0d004f-ae4b-4e91-8ae6-e8b72fc61df6"} + + + + +ON ohne Parameter + --> Einschalten + + +Off + --> Ausschalten \ No newline at end of file