From 0a21b488195f7f81f088f1ffdfdd34911725e702 Mon Sep 17 00:00:00 2001 From: nasseeder1 Date: Tue, 9 Jun 2020 20:41:58 +0000 Subject: [PATCH] 76_SMAPortal: internal code changes, minor bug fixes git-svn-id: https://svn.fhem.de/fhem/trunk@22149 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/76_SMAPortal.pm | 1029 ++++++++++++----------- fhem/contrib/DS_Starter/76_SMAPortal.pm | 116 ++- 3 files changed, 634 insertions(+), 512 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 4e6e478a7..84bca2541 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - changed: 76_SMAPortal: internal code changes, minor bug fixes - bugfix: 70_BRAVIA: fix register procedure - change: 32_withings: added sleep apnea readings - feature: 72_XiaomiDevice: Vacuum segments, Zhimi ZA4 fan readings diff --git a/fhem/FHEM/76_SMAPortal.pm b/fhem/FHEM/76_SMAPortal.pm index 86cb829eb..2e88f1e7d 100644 --- a/fhem/FHEM/76_SMAPortal.pm +++ b/fhem/FHEM/76_SMAPortal.pm @@ -81,6 +81,7 @@ BEGIN { fhemTzOffset FW_makeImage fhemTimeGm + fhemTimeLocal getKeyValue gettimeofday genUUID @@ -134,6 +135,7 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "2.10.1" => "08.06.2020 internal code changes, bug fixes ", "2.10.0" => "03.06.2020 refactored login process ", "2.9.0" => "01.06.2020 add get today statistic data ", "2.8.1" => "31.05.2020 attribute timeout, maxCallCycle deleted ", @@ -229,7 +231,7 @@ sub Initialize { "interval ". "showPassInLog:1,0 ". "userAgent ". - "verbose5Data:none,loginData,balanceData,liveData,weatherData,forecastData,consumerLiveData,consumerDayData,consumerMonthData,consumerYearData ". + "verbose5Data:none,loginData,balanceDayData,liveData,weatherData,forecastData,consumerLiveData,consumerDayData,consumerMonthData,consumerYearData ". $readingFnAttributes; eval { FHEM::Meta::InitMod( __FILE__, $hash ) }; ## no critic 'eval' # für Meta.pm (https://forum.fhem.de/index.php/topic,97589.0.html) @@ -749,18 +751,18 @@ sub GetSetData { ## no cri my ($string) = @_; my ($name,$getp,$setp) = split("\\|",$string); my $hash = $defs{$name}; - my $login_state = 0; + my $errstate = 0; my $useragent = AttrVal($name, "userAgent", "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0)"); my $cookieLocation = AttrVal($name, "cookieLocation", "./log/".$name."_cookie.txt"); my $v5d = AttrVal($name, "verbose5Data", "none"); - my ($ccyeardata_content) = (""); + my $dl = AttrVal($name, "detailLevel", 1); # selected Detail Level my $state = "ok"; my ($reread,$retry) = (0,0); my ($exceed,$newcycle) = (0,0); - my ($forecast_content,$weatherdata_content,$consumerlivedata_content) = ("","",""); - my ($balancedataday_content,$ccdaydata_content,$ccmonthdata_content,$livedata_content) = ("","","",""); - my ($st,$bcd,$lc,$fc,$wc,$cl,$cd,$cm,$cy) = ("","","","","","","","",""); - my ($d,$op); + my ($st,$lc) = ("",""); + my @da = (); + my ($ccdaydata_content,$ccmonthdata_content,$ccyeardata_content) = ("","",""); + my ($d,$op,$paref); if($setp ne "none") { # Verbraucher soll in den Status $op geschaltet werden @@ -796,90 +798,15 @@ sub GetSetData { ## no cri ); handleCounter ($name, "dailyCallCounter"); # Abfragezähler setzen (Anzahl tägliche Wiederholungen von GetSetData) - - my $cts = time; - my $offset = fhemTzOffset($cts); - my $time = int(($cts + $offset) * 1000); # add Timestamp in Millisekunden and UTC - ### Login ############## - my $loginp = $ua->post('https://www.sunnyportal.com/Templates/Start.aspx'); - my $retcode = $loginp->code; - my $location = $loginp->header('Location') // ""; - - if ($loginp->is_success) { - if($v5d eq "loginData") { - Log3 ($name, 5, "$name - Status Login Page: ".$loginp->status_line); - Log3 ($name, 5, "$name - Header Location: ".$location); - Log3 ($name, 5, "$name - Login Page content: ".encode("utf8", $loginp->decoded_content)); - } - - $retcode = $loginp->code; - $location = $loginp->header('Location') // ""; - - if($location ne "/FixedPages/HoManLive.aspx" || $retcode ne "302") { # keine aktive Session -> neuer Login - Log3 ($name, 4, "$name - User not logged in. Try login with credentials ..."); - - # Credentials abrufen - my ($success, $username, $password) = getcredentials($hash,0); - - if(!$success) { - Log3($name, 1, qq{$name - Credentials couldn't be retrieved successfully - make sure you've set it with "set $name credentials "}); - $state = "Credentials couldn't be read"; - $login_state = 0; - - } else { - my $usernameField = "ctl00\$ContentPlaceHolder1\$Logincontrol1\$txtUserName"; - my $passwordField = "ctl00\$ContentPlaceHolder1\$Logincontrol1\$txtPassword"; - my $mempasswd = "ctl00\$ContentPlaceHolder1\$Logincontrol1\$MemorizePassword"; - my $loginField = "__EVENTTARGET"; - my $loginButton = "ctl00\$ContentPlaceHolder1\$Logincontrol1\$LoginBtn"; + $paref = [ $name, $ua, $state, $errstate ]; + ($state, $errstate) = _checkLogin ($paref); - $loginp = $ua->post('https://www.sunnyportal.com/Templates/Start.aspx',[$usernameField => $username, $passwordField => $password, $mempasswd => "on", "__EVENTTARGET" => $loginButton]); - $retcode = $loginp->code; - $location = $loginp->header('Location') // ""; - - if($v5d eq "loginData") { - Log3 ($name, 5, "$name - Status Redirect Page : ".$retcode); - Log3 ($name, 5, "$name - Header Redirect Location: ".$location); - Log3 ($name, 5, "$name - Redirect Page content: ".encode("utf8", $loginp->decoded_content)); - } - - if($location eq "/FixedPages/HoManLive.aspx" && $retcode eq "302") { # Weiterleitung -> Login erfolgeich - Log3 ($name, 3, "$name - Login into SMA-Portal successfully done"); - handleCounter ($name, "dailyIssueCookieCounter"); # Cookie Ausstellungszähler setzen - - BlockingInformParent("FHEM::SMAPortal::setFromBlocking", [$name, "NULL", "NULL", (gettimeofday())[0], "NULL", "NULL"], 0); - $login_state = 1; - - } else { - Log3 ($name, 2, "$name - ERROR - Login into SMA-Portal failed !"); - $state = "login failed - check user and password"; - $login_state = 0; - } - } - } - - } elsif($loginp->is_redirect) { - $retcode = $loginp->code; - $location = $loginp->header('Location') // ""; - if($v5d eq "loginData") { - Log3 ($name, 5, "$name - Redirect return code: ".$retcode); - Log3 ($name, 5, "$name - Redirect Header Location: ".$location); - } - $login_state = 1; - - } else { - $login_state = 0; - $state = $loginp->status_line; - Log3 ($name, 1, "$name - ERROR Login Page: ".$state); - } - - - if(!$login_state) { + if($errstate) { $st = encode_base64 ( $state,""); - return "$name|0|0|$login_state|$getp|$setp|$st"; + return "$name|0|0|$errstate|$getp|$setp|$st"; } ### Verbraucher schalten @@ -914,24 +841,31 @@ sub GetSetData { ## no cri ### Daten abrufen ############################# - if($getp ne "none") { + if($getp ne "none") { - my $dl = AttrVal($name, "detailLevel", 1); # selcted Detail Level + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); + my $cts = fhemTimeLocal(0, 0, 0, $mday, $mon, $year); + my $offset = fhemTzOffset($cts); + my $time = int(($cts + $offset) * 1000); # add Timestamp in Millisekunden and UTC - ### JSON Live-Daten - #################### - Log3 ($name, 4, "$name - Getting Live data"); - my $livedata = $ua->get( 'https://www.sunnyportal.com/homemanager?t='.$time ); # V2.6.2 - $livedata_content = $livedata->content; # JSON Live Daten - - Log3 ($name, 5, "$name - Liva Data received:\n".Dumper ($livedata_content)) if($v5d eq "liveData"); - - ($reread,$retry,$login_state,$state) = analyzeData($hash,$login_state,$state,$livedata); + ### Live-Daten + ################ + my ($livedata,$livedata_content) = _getData ({ name => $name, + ua => $ua, + call => 'https://www.sunnyportal.com/homemanager?t='.$time, + tag => "liveData" + }); + + ($reread,$retry,$errstate,$state) = analyzeData ({ hash => $hash, + errstate => $errstate, + state => $state, + data => $livedata + }); - if(!$login_state) { + if($errstate) { $st = encode_base64 ( $state,""); - return "$name|0|0|$login_state|$getp|$setp|$st"; + return "$name|0|0|$errstate|$getp|$setp|$st"; } goto &GetSetData if($reread); @@ -939,14 +873,14 @@ sub GetSetData { ## no cri # Wiederholung Datenabruf innerhalb eines Cycle my $retc = $hash->{HELPER}{RETRIES}; # aktuelle Retry-Zähler - if($setp eq "none" && $retry && $retc < $maxretries) { # neuer Retry im gleichen Zyklus (nicht wenn Verbraucher schalten) + if($retry && $retc < $maxretries) { # neuer Retry im gleichen Zyklus (nicht wenn Verbraucher schalten) $hash->{HELPER}{RETRIES}++; if($retc == $thold) { # Schwellenwert Leseversuche erreicht -> Cookie File löschen Log3 ($name, 3, qq{$name - Threshold reached, delete cookie and retry ...}); sleep $sleepexc; # Threshold exceed -> Retry mit Cookie löschen $exceed = 1; BlockingInformParent("FHEM::SMAPortal::setFromBlocking", [$name, "NULL", "NULL", "NULL", "NULL", $hash->{HELPER}{RETRIES}], 1); - return "$name|$exceed|$newcycle|$login_state|$getp|$setp"; + return "$name|$exceed|$newcycle|$errstate|$getp|$setp"; } sleep $sleepretry; @@ -956,75 +890,135 @@ sub GetSetData { ## no cri # Wiederholung Datenabruf in einem neuen Cycle my $ac = $hash->{HELPER}{ACTCYCLE}; my $maxcycles = (controlParams $name)[1]; - if($setp eq "none" && $retry && $ac < $maxcycles) { # neuer Zyklus (nicht wenn Verbraucher schalten) + if($retry && $ac < $maxcycles) { # neuer Zyklus (nicht wenn Verbraucher schalten) Log3 ($name, 3, qq{$name - Maximum retries reached, delete cookie and start new cycle ...}); $newcycle = 1; - return "$name|$exceed|$newcycle|$login_state|$getp|$setp"; + return "$name|$exceed|$newcycle|$errstate|$getp|$setp"; } - ### JSON Wetterdaten - ##################### - Log3 ($name, 4, "$name - Getting weather data"); - - my $weatherdata = $ua->get('https://www.sunnyportal.com/Dashboard/Weather'); - $weatherdata_content = $weatherdata->content; - - if($v5d eq "weatherData") { - Log3 ($name, 5, "$name - Return Code: ".$weatherdata->code); - Log3 ($name, 5, "$name - Data received:\n".Dumper decode_json($weatherdata_content)); + if ($livedata_content && $livedata_content !~ m/undefined/ix) { + extractLiveData ({ hash => $hash, + live => $livedata_content, + daref => \@da + }); } - ### JSON Statistic Data Tag (anchorTime beachten !) - #################################################### - Log3 ($name, 4, "$name - Getting statistic day data"); - my $req = HTTP::Request->new( 'POST', 'https://www.sunnyportal.com/FixedPages/HoManEnergyRedesign.aspx/GetLegendWithValues' ); - + + ### Wetterdaten + ##################### + my ($weatherdata,$weatherdata_content) = _getData ({ name => $name, + ua => $ua, + call => 'https://www.sunnyportal.com/Dashboard/Weather', + tag => "weatherData" + }); + + ($reread,$retry,$errstate,$state) = analyzeData ({ hash => $hash, + errstate => $errstate, + state => $state, + data => $weatherdata + }); + + if($errstate) { + $st = encode_base64 ( $state,""); + return "$name|0|0|$errstate|$getp|$setp|$st"; + } + + if ($weatherdata_content && $weatherdata_content !~ m/undefined/ix) { + extractWeatherData ($hash,\@da,$weatherdata_content); + } + + + + + ### Statistic Data Tag (anchorTime beachten !) + ################################################ my $anchort = int ($time / 1000); # anchorTime -> abzurufendes Datum my $tab = 1; # Tab 1 -> Tag , 2->Monat, 3->Jahr, 4->Gesamt - my $cont = qq{"tabNumber":$tab,"anchorTime":$anchort}; + my %fields = ("Content-Type" => "application/json; charset=utf-8"); + my $cont = qq{ {"tabNumber":$tab,"anchorTime":$anchort} }; + + my ($balancedataday,$balancedataday_content) = _putData ({ name => $name, + ua => $ua, + call => 'https://www.sunnyportal.com/FixedPages/HoManEnergyRedesign.aspx/GetLegendWithValues', + fields => \%fields, + content => $cont, + tag => "balanceDayData" + }); + + ($reread,$retry,$errstate,$state) = analyzeData ({ hash => $hash, + errstate => $errstate, + state => $state, + data => $balancedataday + }); - $req->header ( "Content-Type" => "application/json; charset=utf-8" ); - $req->header ( "Content-Length" => 39 ); - $req->content ( "{$cont}" ); - - my $res = $ua->request( $req ); - $balancedataday_content = $res->content; - - if($v5d eq "balanceData") { - Log3 ($name, 5, "$name - Return Code: ".$res->code); - Log3 ($name, 5, "$name - Statistic data received:\n".Dumper decode_json($balancedataday_content)); + if($errstate) { + $st = encode_base64 ( $state,""); + return "$name|0|0|$errstate|$getp|$setp|$st"; } + if ($balancedataday_content && $balancedataday_content !~ m/undefined/ix) { + extractStatisticData ($hash,\@da,$balancedataday_content,"Day"); + } + - ### JSON Forecast Daten - ######################## + + ### Forecast Daten abrufen + ########################### if($dl > 1) { - Log3 ($name, 4, "$name - Getting forecast data"); - - my $forecast_page = $ua->get('https://www.sunnyportal.com/HoMan/Forecast/LoadRecommendationData'); - $forecast_content = $forecast_page->content; + my ($forecastdata,$forecastdata_content) = _getData ({ name => $name, + ua => $ua, + call => 'https://www.sunnyportal.com/HoMan/Forecast/LoadRecommendationData', + tag => "forecastData" + }); + + ($reread,$retry,$errstate,$state) = analyzeData ({ hash => $hash, + errstate => $errstate, + state => $state, + data => $forecastdata + }); - if($v5d eq "forecastData") { - Log3 ($name, 5, "$name - Return Code: ".$forecast_page->code); - Log3 ($name, 5, "$name - Forecast data received:\n".Dumper decode_json($forecast_content)); + if($errstate) { + $st = encode_base64 ($state, ""); + return "$name|0|0|$errstate|$getp|$setp|$st"; + } + + if ($forecastdata_content && $forecastdata_content !~ m/undefined/ix) { + extractPlantData ($hash,\@da,$forecastdata_content); + extractForecastData ($hash,\@da,$forecastdata_content); + extractConsumerData ($hash,\@da,$forecastdata_content); } } - ### JSON Consumer Livedaten und historische Energiedaten - ######################################################### - if($dl > 2) { - Log3 ($name, 4, "$name - Getting consumer live data"); - - my $consumerlivedata = $ua->get('https://www.sunnyportal.com/Homan/ConsumerBalance/GetLiveProxyValues'); - $consumerlivedata_content = $consumerlivedata->content; + + + + ### Consumer Livedaten und historische Energiedaten + ##################################################### + if($dl > 2) { + my ($consumerlivedata,$consumerlivedata_content) = _getData ({ name => $name, + ua => $ua, + call => 'https://www.sunnyportal.com/Homan/ConsumerBalance/GetLiveProxyValues', + tag => "consumerLiveData" + }); + + ($reread,$retry,$errstate,$state) = analyzeData ({ hash => $hash, + errstate => $errstate, + state => $state, + data => $consumerlivedata + }); - if($v5d eq "consumerLiveData") { - Log3 ($name, 5, "$name - Return Code: ".$consumerlivedata->code); - Log3 ($name, 5, "$name - Consumer live data received:\n".Dumper decode_json($consumerlivedata_content)); + if($errstate) { + $st = encode_base64 ( $state,""); + return "$name|0|0|$errstate|$getp|$setp|$st"; } + if ($consumerlivedata_content && $consumerlivedata_content !~ m/undefined/ix) { + extractConsumerLiveData ($hash,\@da,$consumerlivedata_content); + } + + if($hash->{HELPER}{PLANTOID}) { my $PlantOid = $hash->{HELPER}{PLANTOID}; my $dds = (split(/\s+/x, TimeNow()))[0]; @@ -1057,6 +1051,10 @@ sub GetSetData { ## no cri $ccdaydata_content = $ccdaydata->content; Log3 ($name, 5, "$name - Consumer energy data of current day received:\n".Dumper decode_json($ccdaydata_content)) if($v5d eq "consumerDayData"); } + + if ($ccdaydata_content && $ccdaydata_content !~ m/undefined/ix) { + extractConsumerHistData($hash,\@da,$ccdaydata_content,"day"); + } # Energiedaten aktueller Monat Log3 ($name, 4, "$name - Getting consumer energy data of current month"); @@ -1069,7 +1067,11 @@ sub GetSetData { ## no cri if ($ccmonthdata->content =~ m/ConsumerBalanceDeviceInfo/ix) { $ccmonthdata_content = $ccmonthdata->content; Log3 ($name, 5, "$name - Consumer energy data of current month received:\n".Dumper decode_json($ccmonthdata_content)) if($v5d eq "consumerMonthData"); - } + } + + if ($ccmonthdata_content && $ccmonthdata_content !~ m/undefined/ix) { + extractConsumerHistData ($hash,\@da,$ccmonthdata_content,"month"); + } # Energiedaten aktuelles Jahr Log3 ($name, 4, "$name - Getting consumer energy data of current year"); @@ -1082,39 +1084,188 @@ sub GetSetData { ## no cri if ($ccyeardata->content =~ m/ConsumerBalanceDeviceInfo/ix) { $ccyeardata_content = $ccyeardata->content; Log3 ($name, 5, "$name - Consumer energy data of current year received:\n".Dumper decode_json($ccyeardata_content)) if($v5d eq "consumerYearData"); - } + } + + if ($ccyeardata_content && $ccyeardata_content !~ m/undefined/ix) { + extractConsumerHistData ($hash,\@da,$ccyeardata_content,"year"); + } } } } # Daten müssen als Einzeiler zurückgegeben werden - $st = encode_base64 ( $state,""); - $bcd = encode_base64 ( $balancedataday_content, "" ) if($balancedataday_content); - $lc = encode_base64 ( $livedata_content, "" ) if($livedata_content); - $fc = encode_base64 ( $forecast_content, "" ) if($forecast_content); - $wc = encode_base64 ( $weatherdata_content, "" ) if($weatherdata_content); - $cl = encode_base64 ( $consumerlivedata_content, "" ) if($consumerlivedata_content); - $cd = encode_base64 ( $ccdaydata_content, "" ) if($ccdaydata_content); - $cm = encode_base64 ( $ccmonthdata_content, "" ) if($ccmonthdata_content); - $cy = encode_base64 ( $ccyeardata_content, "" ) if($ccyeardata_content); + $st = encode_base64 ( $state, ""); + if(@da) { + $lc = join "###", @da; + $lc = encode_base64 ( $lc, ""); + } -return "$name|$exceed|$newcycle|$login_state|$getp|$setp|$st|$lc|$fc|$wc|$cl|$cd|$cm|$cy|$bcd"; +return "$name|$exceed|$newcycle|$errstate|$getp|$setp|$st|$lc"; +} + +################################################################ +# Login Status checken und ggf. einloggen +################################################################ +sub _checkLogin { + my $paref = shift; + my $name = $paref->[0]; + my $ua = $paref->[1]; + my $state = $paref->[2]; + my $errstate = $paref->[3]; + + my $hash = $defs{$name}; + my $v5d = AttrVal($name, "verbose5Data", "none"); + + my $loginp = $ua->post('https://www.sunnyportal.com/Templates/Start.aspx'); + my $retcode = $loginp->code; + my $location = $loginp->header('Location') // ""; + + if ($loginp->is_success) { + if($v5d eq "loginData") { + Log3 ($name, 5, "$name - Status Login Page: ".$loginp->status_line); + Log3 ($name, 5, "$name - Header Location: ".$location); + Log3 ($name, 5, "$name - Login Page content: ".encode("utf8", $loginp->decoded_content)); + } + + $retcode = $loginp->code; + $location = $loginp->header('Location') // ""; + + if($location ne "/FixedPages/HoManLive.aspx" || $retcode ne "302") { # keine aktive Session -> neuer Login + Log3 ($name, 4, "$name - User not logged in. Try login with credentials ..."); + + # Credentials abrufen + my ($success, $username, $password) = getcredentials($hash,0); + + if(!$success) { + Log3($name, 1, qq{$name - Credentials couldn't be retrieved successfully - make sure you've set it with "set $name credentials "}); + $state = "Credentials couldn't be read"; + $errstate = 1; + + } else { + my $usernameField = "ctl00\$ContentPlaceHolder1\$Logincontrol1\$txtUserName"; + my $passwordField = "ctl00\$ContentPlaceHolder1\$Logincontrol1\$txtPassword"; + my $mempasswd = "ctl00\$ContentPlaceHolder1\$Logincontrol1\$MemorizePassword"; + my $loginField = "__EVENTTARGET"; + my $loginButton = "ctl00\$ContentPlaceHolder1\$Logincontrol1\$LoginBtn"; + + $loginp = $ua->post('https://www.sunnyportal.com/Templates/Start.aspx',[$usernameField => $username, $passwordField => $password, $mempasswd => "on", "__EVENTTARGET" => $loginButton]); + $retcode = $loginp->code; + $location = $loginp->header('Location') // ""; + + if($v5d eq "loginData") { + Log3 ($name, 5, "$name - Status Redirect Page : ".$retcode); + Log3 ($name, 5, "$name - Header Redirect Location: ".$location); + Log3 ($name, 5, "$name - Redirect Page content: ".encode("utf8", $loginp->decoded_content)); + } + + if($location =~ /\/FixedPages\//x && $retcode eq "302") { # Weiterleitung -> Login erfolgeich(Landing Pages können im Portal eingestellt werden!) + Log3 ($name, 3, "$name - Login into SMA-Portal successfully done"); + handleCounter ($name, "dailyIssueCookieCounter"); # Cookie Ausstellungszähler setzen + + BlockingInformParent("FHEM::SMAPortal::setFromBlocking", [$name, "NULL", "NULL", (gettimeofday())[0], "NULL", "NULL"], 0); + $errstate = 0; + + } else { + Log3 ($name, 2, "$name - ERROR - Login into SMA-Portal failed !"); + $state = "login failed - check user and password"; + $errstate = 1; + } + } + } + + } elsif($loginp->is_redirect) { + $retcode = $loginp->code; + $location = $loginp->header('Location') // ""; + if($v5d eq "loginData") { + Log3 ($name, 5, "$name - Redirect return code: ".$retcode); + Log3 ($name, 5, "$name - Redirect Header Location: ".$location); + } + $errstate = 0; + + } else { + $errstate = 1; + $state = $loginp->status_line; + Log3 ($name, 1, "$name - ERROR Login Page: ".$state); + } + +return ($state, $errstate); +} + +################################################################ +# Standard Abruf Daten GET +################################################################ +sub _getData { + my $paref = shift; + my $name = $paref->{name}; + my $ua = $paref->{ua}; + my $call = $paref->{call}; + my $tag = $paref->{tag}; + + my $cont; + + my $v5d = AttrVal($name, "verbose5Data", "none"); + + Log3 ($name, 4, "$name - Getting $tag"); + + my $data = $ua->get( $call ); + my $dcont = $data->content; + + $cont = eval{decode_json($dcont)} or do { $cont = $dcont }; + + if($v5d eq $tag) { + Log3 ($name, 5, "$name - Return Code: ".$data->code); + Log3 ($name, 5, "$name - $tag received:\n".Dumper $cont); + } + +return ($data,$dcont); +} + +################################################################ +# Standard Abruf Daten POST +################################################################ +sub _putData { + my $paref = shift; + my $name = $paref->{name}; + my $ua = $paref->{ua}; + my $call = $paref->{call}; + my $fields = $paref->{fields}; + my $content = $paref->{content}; + my $tag = $paref->{tag}; + my $v5d = AttrVal($name, "verbose5Data", "none"); + + my $cont; + + Log3 ($name, 4, "$name - Getting $tag"); + + my $data = $ua->post( $call, %$fields, Content => $content ); + + my $dcont = $data->content; + + $cont = eval{decode_json($dcont)} or do { $cont = $dcont }; + + if($v5d eq $tag) { + Log3 ($name, 5, "$name - Return Code: ".$data->code); + Log3 ($name, 5, "$name - $tag received:\n".Dumper $cont); + } + +return ($data,$dcont); } ################################################################ ## Verarbeitung empfangene Daten, setzen Readings ################################################################ sub ParseData { ## no critic 'complexity' - my ($string) = @_; - my @a = split("\\|",$string); - my $hash = $defs{$a[0]}; - my $name = $hash->{NAME}; + my $string = shift; + my @a = split("\\|",$string); + my $hash = $defs{$a[0]}; + my $name = $hash->{NAME}; + my @da = (); - my ($login_state,$newcycle,$getp,$setp,$state,$exceed); + my ($errstate,$newcycle,$getp,$setp,$state,$exceed,$lc); $exceed = $a[1]; $newcycle = $a[2]; - $login_state = $a[3]; + $errstate = $a[3]; $getp = $a[4]; $setp = $a[5]; @@ -1147,165 +1298,30 @@ sub ParseData { ## no critic $state = decode_base64($a[6]); - my ($livedata_content,$forecast_content,$weatherdata_content,$consumerlivedata_content); - my ($ccdaydata_content,$ccmonthdata_content,$ccyeardata_content,$balancedataday_content); + if($a[7]) { + $lc = decode_base64($a[7]); + @da = split "###", $lc; + } - $livedata_content = decode_json( decode_base64($a[7]) ) if($a[7]); - $forecast_content = decode_json( decode_base64($a[8]) ) if($a[8]); - $weatherdata_content = decode_json( decode_base64($a[9]) ) if($a[9]); - $consumerlivedata_content = decode_json( decode_base64($a[10]) ) if($a[10]); - $ccdaydata_content = decode_json( decode_base64($a[11]) ) if($a[11]); - $ccmonthdata_content = decode_json( decode_base64($a[12]) ) if($a[12]); - $ccyeardata_content = decode_json( decode_base64($a[13]) ) if($a[13]); - $balancedataday_content = decode_json( decode_base64($a[14]) ) if($a[14]); my $dl = AttrVal($name, "detailLevel", 1); delread($hash, $dl+1); - Log3 ($name, 4, "$name - ##### extracting live data #### "); - readingsBeginUpdate($hash); - my ($FeedIn_done,$GridConsumption_done,$PV_done,$AutarkyQuote_done,$SelfConsumption_done) = (0,0,0,0,0); - my ($SelfConsumptionQuote_done,$SelfSupply_done,$errMsg,$warnMsg,$infoMsg) = (0,0,0,0,0); - my ($batteryin,$batteryout); - - if($getp ne "none") { - for my $k (keys %$livedata_content) { - my $new_val = ""; - if (defined $livedata_content->{$k}) { - if (($livedata_content->{$k} =~ m/ARRAY/i) || ($livedata_content->{$k} =~ m/HASH/ix)) { - Log3 $name, 4, "$name - Livedata content \"$k\": ".($livedata_content->{$k}); - if($livedata_content->{$k} =~ m/ARRAY/ix) { - my $hd0 = $livedata_content->{$k}[0]; - if(!defined $hd0) { - next; - } - chomp $hd0; - $hd0 =~ s/[;']//gx; - $hd0 = encode("utf8", $hd0); - Log3 $name, 4, "$name - Livedata \"$k\": $hd0"; - $new_val = $hd0; - } - } else { - $new_val = $livedata_content->{$k}; - } - - if ($new_val && $k !~ /__type/ix) { - if($k =~ /^FeedIn$/x) { - $new_val = $new_val." W"; - $FeedIn_done = 1 - } - if($k =~ /^GridConsumption$/x) { - $new_val = $new_val." W"; - $GridConsumption_done = 1; - } - if($k =~ /^PV$/x) { - $new_val = $new_val." W"; - $PV_done = 1; - } - if($k =~ /^AutarkyQuote$/x) { - $new_val = $new_val." %"; - $AutarkyQuote_done = 1; - } - if($k =~ /^SelfConsumption$/x) { - $new_val = $new_val." W"; - $SelfConsumption_done = 1; - } - if($k =~ /^SelfConsumptionQuote$/x) { - $new_val = $new_val." %"; - $SelfConsumptionQuote_done = 1; - } - if($k =~ /^SelfSupply$/x) { - $new_val = $new_val." W"; - $SelfSupply_done = 1; - } - if($k =~ /^TotalConsumption$/x) { - $new_val = $new_val." W"; - } - if($k =~ /^BatteryIn$/x) { - $new_val = $new_val." W"; - $batteryin = 1; - } - if($k =~ /^BatteryOut$/x) { - $new_val = $new_val." W"; - $batteryout = 1; - } - - if($k =~ /^ErrorMessages$/x) { $errMsg = 1; $new_val = qq{Message got from SMA Sunny Portal:
$new_val};} - if($k =~ /^WarningMessages$/x) { $warnMsg = 1; $new_val = qq{Message got from SMA Sunny Portal:
$new_val};} - if($k =~ /^InfoMessages$/x) { $infoMsg = 1; $new_val = qq{Message got from SMA Sunny Portal:
$new_val};} + for my $elem (@da) { + my ($rn,$rval) = split ":", $elem, 2; + readingsBulkUpdate($hash, $rn, $rval); + } - Log3 ($name, 4, "$name - $k - $new_val"); - readingsBulkUpdate($hash, "L1_$k", $new_val); - } - } - } - - readingsBulkUpdate($hash, "L1_FeedIn", "0 W") if(!$FeedIn_done); - readingsBulkUpdate($hash, "L1_GridConsumption", "0 W") if(!$GridConsumption_done); - readingsBulkUpdate($hash, "L1_PV", "0 W") if(!$PV_done); - readingsBulkUpdate($hash, "L1_AutarkyQuote", "0 %") if(!$AutarkyQuote_done); - readingsBulkUpdate($hash, "L1_SelfConsumption", "0 W") if(!$SelfConsumption_done); - readingsBulkUpdate($hash, "L1_SelfConsumptionQuote", "0 %") if(!$SelfConsumptionQuote_done); - readingsBulkUpdate($hash, "L1_SelfSupply", "0 W") if(!$SelfSupply_done); - - if(defined $batteryin || defined $batteryout) { - readingsBulkUpdate($hash, "L1_BatteryIn", "0 W") if(!$batteryin); - readingsBulkUpdate($hash, "L1_BatteryOut", "0 W") if(!$batteryout); - } - - readingsEndUpdate($hash, 1); - - } - - readingsDelete($hash,"L1_ErrorMessages") if(!$errMsg); - readingsDelete($hash,"L1_WarningMessages") if(!$warnMsg); - readingsDelete($hash,"L1_InfoMessages") if(!$infoMsg); - - if ($forecast_content && $forecast_content !~ m/undefined/ix && $dl >= 2) { - # Auswertung der Forecast Daten - extractForecastData ($hash,$forecast_content); - extractPlantData ($hash,$forecast_content); - extractConsumerData ($hash,$forecast_content); - } - - if ($consumerlivedata_content && $consumerlivedata_content !~ m/undefined/ix && $dl > 2) { - # Auswertung Consumer Live Daten - extractConsumerLiveData ($hash,$consumerlivedata_content); - } - - if ($ccdaydata_content && $ccdaydata_content !~ m/undefined/ix && $dl > 2) { - # Auswertung Consumer Energiedaten des aktuellen Tages - extractConsumerHistData($hash,$ccdaydata_content,"day"); - } - - if ($ccmonthdata_content && $ccmonthdata_content !~ m/undefined/ix && $dl > 2) { - # Auswertung Consumer Energiedaten des aktuellen Monats - extractConsumerHistData ($hash,$ccmonthdata_content,"month"); - } - - if ($ccyeardata_content && $ccyeardata_content !~ m/undefined/ix && $dl > 2) { - # Auswertung Consumer Energiedaten des aktuellen Jahres - extractConsumerHistData ($hash,$ccyeardata_content,"year"); - } - - if ($weatherdata_content && $weatherdata_content !~ m/undefined/ix) { - # Auswertung Wetterdaten - extractWeatherData ($hash,$weatherdata_content); - } - - if ($balancedataday_content && $balancedataday_content !~ m/undefined/ix) { - # Auswertung Statistik Daten Tag - extractStatisticData ($hash,$balancedataday_content,"Day"); - } + readingsEndUpdate($hash, 1); my $pv = ReadingsNum($name, "L1_PV" , 0); my $fi = ReadingsNum($name, "L1_FeedIn" , 0); my $gc = ReadingsNum($name, "L1_GridConsumption", 0); my $sum = $fi-$gc; - if($login_state && !$pv && !$fi && !$gc) { + if(!$errstate && !$pv && !$fi && !$gc) { # keine Anlagendaten vorhanden $state = "Data can't be retrieved from SMA-Portal. Reread at next scheduled cycle."; Log3 ($name, 2, "$name - $state"); @@ -1313,10 +1329,10 @@ sub ParseData { ## no critic readingsBeginUpdate($hash); - if($login_state) { + if(!$errstate) { if($setp ne "none") { my ($d,$op) = split(":",$setp); - $op = ($op eq "auto")?"off (automatic)":$op; + $op = ($op eq "auto") ? "off (automatic)" : $op; readingsBulkUpdate($hash, "L3_${d}_Switch", $op); } readingsBulkUpdate($hash, "lastCycleTime", $ctime); @@ -1325,6 +1341,7 @@ sub ParseData { ## no critic } else { readingsBulkUpdate($hash, "L1_Login-Status", "failed"); } + readingsBulkUpdate($hash, "state", $state); readingsEndUpdate($hash, 1); @@ -1377,17 +1394,92 @@ sub delcookiefile { return ($err); } +################################################################ +## Auswertung Live Daten +################################################################ +sub extractLiveData { + my $paref = shift; + my $hash = $paref->{hash}; + my $live = $paref->{live}; + my $daref = $paref->{daref}; + my $name = $hash->{NAME}; + my $val = ""; + + Log3 ($name, 4, "$name - ##### extracting live data #### "); + + $live = eval{decode_json($live)} or do { Log3 ($name, 2, "$name - ERROR - can't decode JSON Data"); + return; + }; + + my ($errMsg,$warnMsg,$infoMsg) = (0,0,0); + + if (ref $live eq "HASH") { + push @$daref, "L1_FeedIn:" .($live->{FeedIn} // 0)." W"; + push @$daref, "L1_GridConsumption:" .($live->{GridConsumption} // 0)." W"; + push @$daref, "L1_PV:" .($live->{PV} // 0)." W"; + push @$daref, "L1_AutarkyQuote:" .($live->{AutarkyQuote} // 0)." %"; + push @$daref, "L1_SelfConsumption:" .($live->{SelfConsumption} // 0)." W"; + push @$daref, "L1_SelfConsumptionQuote:" .($live->{SelfConsumptionQuote} // 0)." %"; + push @$daref, "L1_SelfSupply:" .($live->{SelfSupply} // 0)." W"; + push @$daref, "L1_TotalConsumption:" .($live->{TotalConsumption} // 0)." W"; + + push @$daref, "L1_BatteryIn:" .$live->{BatteryIn}. " W" if(defined $live->{BatteryIn}); + push @$daref, "L1_BatteryOut:" .$live->{BatteryOut}." W" if(defined $live->{BatteryOut}); + + if($live->{ErrorMessages}[0]) { + my @em; + $errMsg = 1; + for my $a (@{$live->{ErrorMessages}}) { + push @em, $a; + } + $val = join " ", @em if(@em); + push @$daref, "L1_ErrorMessages:".qq{Message got from SMA Sunny Portal:
$val}; + } + + if($live->{WarningMessages}[0]) { + my @wm; + $warnMsg = 1; + for my $a (@{$live->{WarningMessages}}) { + push @wm, $a; + } + $val = join " ", @wm if(@wm); + push @$daref, "L1_WarningMessages:".qq{Message got from SMA Sunny Portal:
$val}; + } + + if($live->{InfoMessages}[0]) { + my @im; + $infoMsg = 1; + for my $a (@{$live->{InfoMessages}}) { + push @im, $a; + } + $val = join " ", @im if(@im); + push @$daref, "L1_InfoMessages:".qq{Message got from SMA Sunny Portal:
$val}; + } + } + + BlockingInformParent("FHEM::SMAPortal::delReadingFromBlocking", [$name, "L1_ErrorMessages"], 1) if(!$errMsg); + BlockingInformParent("FHEM::SMAPortal::delReadingFromBlocking", [$name, "L1_WarningMessages"], 1) if(!$warnMsg); + BlockingInformParent("FHEM::SMAPortal::delReadingFromBlocking", [$name, "L1_InfoMessages"], 1) if(!$infoMsg); + +return; +} + ################################################################ ## Auswertung Forecast Daten ################################################################ sub extractForecastData { ## no critic 'complexity' - my ($hash,$forecast) = @_; - my $name = $hash->{NAME}; - my @da = (); + my $hash = shift; + my $daref = shift; + my $forecast = shift; + my $name = $hash->{NAME}; my $dl = AttrVal($name, "detailLevel", 1); Log3 ($name, 4, "$name - ##### extracting forecast data #### "); + + $forecast = eval{decode_json($forecast)} or do { Log3 ($name, 2, "$name - ERROR - can't decode JSON Data"); + return; + }; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); $year += 1900; @@ -1396,16 +1488,7 @@ sub extractForecastData { ## no critic 'complexity' my $PV_sum = 0; my $consum_sum = 0; - my $sum = 0; - - my $plantOid = $forecast->{'ForecastTimeframes'}->{'PlantOid'}; - $hash->{HELPER}{PLANTOID} = $plantOid; # wichtig für erweiterte Selektionen - if ($hash->{HELPER}{PLANTOID}) { - Log3 ($name, 4, "$name - Plant ID set to: ".$hash->{HELPER}{PLANTOID}); - } else { - Log3 ($name, 4, "$name - Plant ID not set !"); - } - + my $sum = 0; # Counter for forecast objects my $obj_nr = 0; @@ -1475,21 +1558,21 @@ sub extractForecastData { ## no critic 'complexity' my $time_str = "ThisHour"; $time_str = "NextHour".sprintf("%02d", $obj_nr) if($fc_diff_hours>0); if($time_str =~ /NextHour/x && $dl >= 4) { - push @da, "L4_${time_str}_Time:". TimeAdjust($hash,$fc_obj->{'TimeStamp'}->{'DateTime'},$tkind); - push @da, "L4_${time_str}_PvMeanPower:". int( $fc_obj->{'PvMeanPower'}->{'Amount'} )." Wh"; # in W als Durchschnitt geliefet, d.h. eine Stunde -> Wh - push @da, "L4_${time_str}_Consumption:". int( $fc_obj->{'ConsumptionForecast'}->{'Amount'} / 3600 )." Wh"; # {'ConsumptionForecast'}->{'Amount'} wird als J = Ws geliefert - push @da, "L4_${time_str}_IsConsumptionRecommended:". ($fc_obj->{'IsConsumptionRecommended'} ? "yes" : "no"); - push @da, "L4_${time_str}_Total:". (int($fc_obj->{'PvMeanPower'}->{'Amount'}) - int($fc_obj->{'ConsumptionForecast'}->{'Amount'} / 3600))." Wh"; + push @$daref, "L4_${time_str}_Time:". TimeAdjust($hash,$fc_obj->{'TimeStamp'}->{'DateTime'},$tkind); + push @$daref, "L4_${time_str}_PvMeanPower:". int( $fc_obj->{'PvMeanPower'}->{'Amount'} )." Wh"; # in W als Durchschnitt geliefet, d.h. eine Stunde -> Wh + push @$daref, "L4_${time_str}_Consumption:". int( $fc_obj->{'ConsumptionForecast'}->{'Amount'} / 3600 )." Wh"; # {'ConsumptionForecast'}->{'Amount'} wird als J = Ws geliefert + push @$daref, "L4_${time_str}_IsConsumptionRecommended:". ($fc_obj->{'IsConsumptionRecommended'} ? "yes" : "no"); + push @$daref, "L4_${time_str}_Total:". (int($fc_obj->{'PvMeanPower'}->{'Amount'}) - int($fc_obj->{'ConsumptionForecast'}->{'Amount'} / 3600))." Wh"; # add WeatherId Helper to show weather icon $hash->{HELPER}{"L4_".${time_str}."_WeatherId"} = int($fc_obj->{'WeatherId'}) if(defined $fc_obj->{'WeatherId'}); } if($time_str =~ /ThisHour/x && $dl >= 2) { - push @da, "L2_${time_str}_Time:". TimeAdjust($hash,$fc_obj->{'TimeStamp'}->{'DateTime'},$tkind); - push @da, "L2_${time_str}_PvMeanPower:". int( $fc_obj->{'PvMeanPower'}->{'Amount'} )." Wh"; # in W als Durchschnitt geliefet, d.h. eine Stunde -> Wh - push @da, "L2_${time_str}_Consumption:". int( $fc_obj->{'ConsumptionForecast'}->{'Amount'} / 3600 )." Wh"; # {'ConsumptionForecast'}->{'Amount'} wird als J = Ws geliefert - push @da, "L2_${time_str}_IsConsumptionRecommended:". ($fc_obj->{'IsConsumptionRecommended'} ? "yes" : "no"); - push @da, "L2_${time_str}_Total:". (int($fc_obj->{'PvMeanPower'}->{'Amount'}) - int($fc_obj->{'ConsumptionForecast'}->{'Amount'} / 3600))." Wh"; + push @$daref, "L2_${time_str}_Time:". TimeAdjust($hash,$fc_obj->{'TimeStamp'}->{'DateTime'},$tkind); + push @$daref, "L2_${time_str}_PvMeanPower:". int( $fc_obj->{'PvMeanPower'}->{'Amount'} )." Wh"; # in W als Durchschnitt geliefet, d.h. eine Stunde -> Wh + push @$daref, "L2_${time_str}_Consumption:". int( $fc_obj->{'ConsumptionForecast'}->{'Amount'} / 3600 )." Wh"; # {'ConsumptionForecast'}->{'Amount'} wird als J = Ws geliefert + push @$daref, "L2_${time_str}_IsConsumptionRecommended:". ($fc_obj->{'IsConsumptionRecommended'} ? "yes" : "no"); + push @$daref, "L2_${time_str}_Total:". (int($fc_obj->{'PvMeanPower'}->{'Amount'}) - int($fc_obj->{'ConsumptionForecast'}->{'Amount'} / 3600))." Wh"; # add WeatherId Helper to show weather icon $hash->{HELPER}{"L2_".${time_str}."_WeatherId"} = int($fc_obj->{'WeatherId'}) if(defined $fc_obj->{'WeatherId'}); @@ -1502,31 +1585,22 @@ sub extractForecastData { ## no critic 'complexity' } if($dl >= 2) { - push @da, "L2_Next04Hours_Consumption:". int( $nextFewHoursSum{'Consumption'} )." Wh"; - push @da, "L2_Next04Hours_PV:". int( $nextFewHoursSum{'PV'} )." Wh"; - push @da, "L2_Next04Hours_Total:". int( $nextFewHoursSum{'Total'} )." Wh"; - push @da, "L2_Next04Hours_IsConsumptionRecommended:". int( $nextFewHoursSum{'ConsumpRcmd'} )." h"; - push @da, "L2_ForecastToday_Consumption:". $consum_sum." Wh"; - push @da, "L2_ForecastToday_PV:". $PV_sum." Wh"; - push @da, "L2_RestOfDay_Consumption:". int( $restOfDaySum{'Consumption'} )." Wh"; - push @da, "L2_RestOfDay_PV:". int( $restOfDaySum{'PV'} )." Wh"; - push @da, "L2_RestOfDay_Total:". int( $restOfDaySum{'Total'} )." Wh"; - push @da, "L2_RestOfDay_IsConsumptionRecommended:". int( $restOfDaySum{'ConsumpRcmd'} )." h"; - push @da, "L2_Tomorrow_Consumption:". int( $tomorrowSum{'Consumption'} )." Wh"; - push @da, "L2_Tomorrow_PV:". int( $tomorrowSum{'PV'} )." Wh"; - push @da, "L2_Tomorrow_Total:". int( $tomorrowSum{'Total'} )." Wh"; - push @da, "L2_Tomorrow_IsConsumptionRecommended:". int( $tomorrowSum{'ConsumpRcmd'} )." h"; + push @$daref, "L2_Next04Hours_Consumption:". int( $nextFewHoursSum{'Consumption'} )." Wh"; + push @$daref, "L2_Next04Hours_PV:". int( $nextFewHoursSum{'PV'} )." Wh"; + push @$daref, "L2_Next04Hours_Total:". int( $nextFewHoursSum{'Total'} )." Wh"; + push @$daref, "L2_Next04Hours_IsConsumptionRecommended:". int( $nextFewHoursSum{'ConsumpRcmd'} )." h"; + push @$daref, "L2_ForecastToday_Consumption:". $consum_sum." Wh"; + push @$daref, "L2_ForecastToday_PV:". $PV_sum." Wh"; + push @$daref, "L2_RestOfDay_Consumption:". int( $restOfDaySum{'Consumption'} )." Wh"; + push @$daref, "L2_RestOfDay_PV:". int( $restOfDaySum{'PV'} )." Wh"; + push @$daref, "L2_RestOfDay_Total:". int( $restOfDaySum{'Total'} )." Wh"; + push @$daref, "L2_RestOfDay_IsConsumptionRecommended:". int( $restOfDaySum{'ConsumpRcmd'} )." h"; + push @$daref, "L2_Tomorrow_Consumption:". int( $tomorrowSum{'Consumption'} )." Wh"; + push @$daref, "L2_Tomorrow_PV:". int( $tomorrowSum{'PV'} )." Wh"; + push @$daref, "L2_Tomorrow_Total:". int( $tomorrowSum{'Total'} )." Wh"; + push @$daref, "L2_Tomorrow_IsConsumptionRecommended:". int( $tomorrowSum{'ConsumpRcmd'} )." h"; } - readingsBeginUpdate($hash); # generiere Readings - - for my $elem (@da) { - my ($rn,$rval) = split ":", $elem, 2; - readingsBulkUpdate($hash, $rn, $rval); - } - - readingsEndUpdate($hash, 1); - return; } @@ -1534,12 +1608,17 @@ return; ## Auswertung Wetterdaten ################################################################ sub extractWeatherData { - my ($hash,$weather) = @_; - my $name = $hash->{NAME}; - my @da = (); + my $hash = shift; + my $daref = shift; + my $weather = shift; + my $name = $hash->{NAME}; Log3 ($name, 4, "$name - ##### extracting weather data #### "); + $weather = eval{decode_json($weather)} or do { Log3 ($name, 2, "$name - ERROR - can't decode JSON Data"); + return; + }; + for my $k (keys %$weather) { next if(!$k); @@ -1552,22 +1631,13 @@ sub extractWeatherData { my $symbol = encode("utf8", $weather->{$k}{TemperatureSymbol}); my $temp = sprintf("%.1f", $weather->{$k}{Temperature}); my $wdesc = $weather->{$k}{WeatherDescription}; - $wdesc =~ s/t/T/x; + $wdesc =~ s/t/T/x if($wdesc =~ /^t/x); $day =~ s/t/T/x; - push @da, "L1_${day}_Temperature:$temp $symbol"; - push @da, "L1_${day}_WeatherDescription:$wdesc"; + push @$daref, "L1_${day}_Temperature:$temp $symbol"; + push @$daref, "L1_${day}_WeatherDescription:$wdesc"; } - } - - readingsBeginUpdate($hash); - - for my $elem (@da) { - my ($rn,$rval) = split ":", $elem, 2; - readingsBulkUpdate($hash, $rn, $rval); - } - - readingsEndUpdate($hash, 1); + } return; } @@ -1577,14 +1647,19 @@ return; # $period = Day | Month | Year ################################################################ sub extractStatisticData { - my ($hash,$statistic,$period) = @_; - my $name = $hash->{NAME}; - + my $hash = shift; + my $daref = shift; + my $statistic = shift; + my $period = shift; + my $name = $hash->{NAME}; my $sd; - my @da = (); Log3 ($name, 4, "$name - ##### extracting statistic data #### "); + $statistic = eval{decode_json($statistic)} or do { Log3 ($name, 2, "$name - ERROR - can't decode JSON Data"); + return; + }; + if(ref $statistic eq "HASH") { $sd = decode_json ($statistic->{d}); } @@ -1593,18 +1668,9 @@ sub extractStatisticData { for my $a (@$sd) { # jedes ARRAY-Element ist ein HASH my $k = $a->{Key}; my $v = $a->{Value}; - push @da, "L1_Today_${k}:$v" if(defined $statkeys{$k}); + push @$daref, "L1_Today_${k}:$v" if(defined $statkeys{$k}); } - } - - readingsBeginUpdate($hash); # generiere Readings - - for my $elem (@da) { - my ($rn,$rval) = split ":", $elem, 2; - readingsBulkUpdate($hash, $rn, $rval); - } - - readingsEndUpdate($hash, 1); + } return; } @@ -1613,32 +1679,36 @@ return; ## Auswertung Anlagendaten ################################################################ sub extractPlantData { - my ($hash,$forecast) = @_; - my $name = $hash->{NAME}; + my $hash = shift; + my $daref = shift; + my $forecast = shift; + my $name = $hash->{NAME}; my ($amount,$unit); - my @da = (); Log3 ($name, 4, "$name - ##### extracting plant data #### "); + $forecast = eval{decode_json($forecast)} or do { Log3 ($name, 2, "$name - ERROR - can't decode JSON Data"); + return; + }; + + my $plantOid = $forecast->{'ForecastTimeframes'}->{'PlantOid'}; + if ($plantOid) { # wichtig für erweiterte Selektionen + Log3 ($name, 4, "$name - Plant ID: ".$plantOid); + $hash->{HELPER}{PLANTOID} = $plantOid; + } else { + Log3 ($name, 4, "$name - Plant ID not set !"); + } + my $ppp = $forecast->{'PlantPeakPower'}; if($ppp) { $amount = $forecast->{'PlantPeakPower'}{'Amount'}; $unit = $forecast->{'PlantPeakPower'}{'StandardUnit'}{'Symbol'}; - push @da, "L2_PlantPeakPower:$amount $unit"; + push @$daref, "L2_PlantPeakPower:$amount $unit"; Log3 $name, 4, "$name - Plantdata \"PlantPeakPower Amount\": $amount"; Log3 $name, 4, "$name - Plantdata \"PlantPeakPower Symbol\": $unit"; } - - readingsBeginUpdate($hash); # generiere Readings - - for my $elem (@da) { - my ($rn,$rval) = split ":", $elem, 2; - readingsBulkUpdate($hash, $rn, $rval); - } - - readingsEndUpdate($hash, 1); return; } @@ -1647,16 +1717,21 @@ return; ## Auswertung Consumer Data ################################################################ sub extractConsumerData { - my ($hash,$forecast) = @_; - my $name = $hash->{NAME}; + my $hash = shift; + my $daref = shift; + my $forecast = shift; + my $name = $hash->{NAME}; my %consumers; my ($key,$val); - my @da = (); my $dl = AttrVal($name, "detailLevel", 1); Log3 ($name, 4, "$name - ##### extracting consumer data #### "); + $forecast = eval{decode_json($forecast)} or do { Log3 ($name, 2, "$name - ERROR - can't decode JSON Data"); + return; + }; + # Schleife über alle Consumer Objekte my $i = 0; for my $c (@{$forecast->{'Consumers'}}) { @@ -1699,30 +1774,21 @@ sub extractConsumerData { my $re = "L3_${cn}_PlannedOpTimeEnd"; my $rp = "L3_${cn}_Planned"; if($pos) { - push @da, "$rb:$pos"; - push @da, "$rp:yes"; + push @$daref, "$rb:$pos"; + push @$daref, "$rp:yes"; } else { - push @da, "$rb:undefined"; - push @da, "$rp:no"; + push @$daref, "$rb:undefined"; + push @$daref, "$rp:no"; } if($poe) { - push @da, "$re:$poe"; + push @$daref, "$re:$poe"; } else { - push @da, "$re:undefined"; + push @$daref, "$re:undefined"; } } } } - readingsBeginUpdate($hash); # generiere Readings - - for my $elem (@da) { - my ($rn,$rval) = split ":", $elem, 2; - readingsBulkUpdate($hash, $rn, $rval); - } - - readingsEndUpdate($hash, 1); - return; } @@ -1730,14 +1796,19 @@ return; ## Auswertung Consumer Livedata ################################################################ sub extractConsumerLiveData { - my ($hash,$clivedata) = @_; - my $name = $hash->{NAME}; + my $hash = shift; + my $daref = shift; + my $clivedata = shift; + my $name = $hash->{NAME}; my %consumers; my ($key,$val,$i,$res); - my @da = (); Log3 ($name, 4, "$name - ##### extracting consumer live data #### "); + $clivedata = eval{decode_json($clivedata)} or do { Log3 ($name, 2, "$name - ERROR - can't decode JSON Data"); + return; + }; + # allen Consumer Objekte die ID zuordnen $i = 0; for my $c (@{$clivedata->{'MeasurementData'}}) { @@ -1754,7 +1825,7 @@ sub extractConsumerLiveData { $hash->{HELPER}{CONSUMER}{$i}{SerialNumber} = $c->{'SerialNumber'}; $hash->{HELPER}{CONSUMER}{$i}{SUSyID} = $c->{'SUSyID'}; - push @da, "L3_${cn}_Power:".$cpower." W" if(defined $cpower); + push @$daref, "L3_${cn}_Power:".$cpower." W" if(defined $cpower); $i++; } @@ -1783,22 +1854,13 @@ sub extractConsumerLiveData { $res = "undefined"; } - push @da, "L3_${cn}_Switch:$res"; - push @da, "L3_${cn}_SwitchLastTime:$ltchange"; + push @$daref, "L3_${cn}_Switch:$res"; + push @$daref, "L3_${cn}_SwitchLastTime:$ltchange"; $i++; } } - readingsBeginUpdate($hash); # generiere Readings - - for my $elem (@da) { - my ($rn,$rval) = split ":", $elem, 2; - readingsBulkUpdate($hash, $rn, $rval); - } - - readingsEndUpdate($hash, 1); - return; } @@ -1807,14 +1869,20 @@ return; ## $tf = Time Frame ################################################################ sub extractConsumerHistData { ## no critic 'complexity' - my ($hash,$chdata,$tf) = @_; - my $name = $hash->{NAME}; + my $hash = shift; + my $daref = shift; + my $chdata = shift; + my $tf = shift; + my $name = $hash->{NAME}; my %consumers; - my @da = (); - my ($key,$val,$i,$res,$gcr,$gct,$pcr,$pct,$tct,$bcr,$bct); + my ($i,$gcr,$gct,$pcr,$pct,$tct,$bcr,$bct); Log3 ($name, 4, "$name - ##### extracting consumer history data #### "); + $chdata = eval{decode_json($chdata)} or do { Log3 ($name, 2, "$name - ERROR - can't decode JSON Data"); + return; + }; + my $bataval = (defined(ReadingsNum($name,"L1_BatteryIn", undef)) || defined(ReadingsNum($name,"L1_BatteryOut", undef)))?1:0; # Identifikation ist Battery vorhanden ? # allen Consumer Objekte die ID zuordnen @@ -1838,33 +1906,24 @@ sub extractConsumerHistData { # $bct = $c->{'TotalEnergyMix'}{'BatteryConsumptionTotal'}; # Anteil der Batterie-Nutzung im Timeframe am Gesamtverbrauch in Wh } - push @da, "L3_${cn}_EnergyTotalDay:". sprintf("%.0f", $cpower). " Wh" if(defined($cpower) && $tf eq "day"); - push @da, "L3_${cn}_EnergyTotalMonth:". sprintf("%.0f", $cpower). " Wh" if(defined($cpower) && $tf eq "month"); - push @da, "L3_${cn}_EnergyTotalYear:". sprintf("%.0f", $cpower). " Wh" if(defined($cpower) && $tf eq "year"); - push @da, "L3_${cn}_EnergyRelativeMonthGrid:". sprintf("%.0f", $gcr). " %" if(defined($gcr) && $tf eq "month"); - push @da, "L3_${cn}_EnergyTotalMonthGrid:". sprintf("%.0f", $gct). " Wh" if(defined($gct) && $tf eq "month"); - push @da, "L3_${cn}_EnergyRelativeMonthPV:". sprintf("%.0f", $pcr). " %" if(defined($pcr) && $tf eq "month"); - push @da, "L3_${cn}_EnergyTotalMonthPV:". sprintf("%.0f", $pct). " Wh" if(defined($pct) && $tf eq "month"); - push @da, "L3_${cn}_EnergyRelativeMonthBatt:". sprintf("%.0f", $bcr). " %" if(defined($bcr) && $bataval && $tf eq "month"); - push @da, "L3_${cn}_EnergyTotalMonthBatt:". sprintf("%.0f", $bct). " Wh" if(defined($bct) && $bataval && $tf eq "month"); - push @da, "L3_${cn}_EnergyRelativeYearGrid:". sprintf("%.0f", $gcr). " %" if(defined($gcr) && $tf eq "year"); - push @da, "L3_${cn}_EnergyTotalYearGrid:". sprintf("%.0f", $gct). " Wh" if(defined($gct) && $tf eq "year"); - push @da, "L3_${cn}_EnergyRelativeYearPV:". sprintf("%.0f", $pcr). " %" if(defined($pcr) && $tf eq "year"); - push @da, "L3_${cn}_EnergyTotalYearPV:". sprintf("%.0f", $pct). " Wh" if(defined($pct) && $tf eq "year"); - push @da, "L3_${cn}_EnergyRelativeYearBatt:". sprintf("%.0f", $bcr). " %" if(defined($bcr) && $bataval && $tf eq "year"); - push @da, "L3_${cn}_EnergyTotalYearBatt:". sprintf("%.0f", $bct). " Wh" if(defined($bct) && $bataval && $tf eq "year"); + push @$daref, "L3_${cn}_EnergyTotalDay:". sprintf("%.0f", $cpower). " Wh" if(defined($cpower) && $tf eq "day"); + push @$daref, "L3_${cn}_EnergyTotalMonth:". sprintf("%.0f", $cpower). " Wh" if(defined($cpower) && $tf eq "month"); + push @$daref, "L3_${cn}_EnergyTotalYear:". sprintf("%.0f", $cpower). " Wh" if(defined($cpower) && $tf eq "year"); + push @$daref, "L3_${cn}_EnergyRelativeMonthGrid:". sprintf("%.0f", $gcr). " %" if(defined($gcr) && $tf eq "month"); + push @$daref, "L3_${cn}_EnergyTotalMonthGrid:". sprintf("%.0f", $gct). " Wh" if(defined($gct) && $tf eq "month"); + push @$daref, "L3_${cn}_EnergyRelativeMonthPV:". sprintf("%.0f", $pcr). " %" if(defined($pcr) && $tf eq "month"); + push @$daref, "L3_${cn}_EnergyTotalMonthPV:". sprintf("%.0f", $pct). " Wh" if(defined($pct) && $tf eq "month"); + push @$daref, "L3_${cn}_EnergyRelativeMonthBatt:". sprintf("%.0f", $bcr). " %" if(defined($bcr) && $bataval && $tf eq "month"); + push @$daref, "L3_${cn}_EnergyTotalMonthBatt:". sprintf("%.0f", $bct). " Wh" if(defined($bct) && $bataval && $tf eq "month"); + push @$daref, "L3_${cn}_EnergyRelativeYearGrid:". sprintf("%.0f", $gcr). " %" if(defined($gcr) && $tf eq "year"); + push @$daref, "L3_${cn}_EnergyTotalYearGrid:". sprintf("%.0f", $gct). " Wh" if(defined($gct) && $tf eq "year"); + push @$daref, "L3_${cn}_EnergyRelativeYearPV:". sprintf("%.0f", $pcr). " %" if(defined($pcr) && $tf eq "year"); + push @$daref, "L3_${cn}_EnergyTotalYearPV:". sprintf("%.0f", $pct). " Wh" if(defined($pct) && $tf eq "year"); + push @$daref, "L3_${cn}_EnergyRelativeYearBatt:". sprintf("%.0f", $bcr). " %" if(defined($bcr) && $bataval && $tf eq "year"); + push @$daref, "L3_${cn}_EnergyTotalYearBatt:". sprintf("%.0f", $bct). " Wh" if(defined($bct) && $bataval && $tf eq "year"); $i++; } - - readingsBeginUpdate($hash); # generiere Readings - - for my $elem (@da) { - my ($rn,$rval) = split ":", $elem, 2; - readingsBulkUpdate($hash, $rn, $rval); - } - - readingsEndUpdate($hash, 1); return; } @@ -2011,18 +2070,34 @@ sub setFromBlocking { return 1; } +################################################################ +# Reading aus BlockingCall heraus löschen +# Erwartete Liste: +# @params = $name,$reading +################################################################ +sub delReadingFromBlocking { + my $name = shift; + my $reading = shift; + my $hash = $defs{$name}; + + readingsDelete($hash, $reading); + +return 1; +} + ################################################################ # analysiere abgerufene Daten ################################################################ sub analyzeData { ## no critic 'complexity' - my $hash = shift; - my $login_state = shift; - my $state = shift; - my $ad = shift; + my $paref = shift; + my $hash = $paref->{hash}; + my $errstate = $paref->{errstate}; + my $state = $paref->{state}; + my $ad = $paref->{data}; my $name = $hash->{NAME}; my ($reread,$retry) = (0,0); - my $data = ""; - my @da = (); + my $data = ""; + my @da = (); my $v5d = AttrVal($name, "verbose5Data", "none"); my $ad_content = encode("utf8", $ad->decoded_content); @@ -2054,24 +2129,24 @@ sub analyzeData { ## no if($k =~ m/WarningMessages/x && $val =~ /Updating of the live data was interrupted/) { ## no critic 'regular expression' # Regular expression without "/x" flag nicht anwenden !!! Log3 $name, 3, "$name - Updating of the live data was interrupted. $attstr"; $retry = 1; - return ($reread,$retry,$login_state,$state); + return ($reread,$retry,$errstate,$state); } if($k =~ m/WarningMessages/x && $val =~ /The current consumption could not be determined. The current purchased electricity is unknown/) { ## no critic 'regular expression' # Regular expression without "/x" flag nicht anwenden !!! Log3 $name, 3, "$name - The current consumption could not be determined. The current purchased electricity is unknown. $attstr"; $retry = 1; - return ($reread,$retry,$login_state,$state); + return ($reread,$retry,$errstate,$state); } if($k =~ m/ErrorMessages/x && $val =~ /Communication with the Sunny Home Manager is currently not possible/) { ## no critic 'regular expression' # Regular expression without "/x" flag nicht anwenden !!! # Energiedaten konnten nicht ermittelt werden, Daten neu lesen mit Zeitverzögerung Log3 $name, 3, "$name - Communication with the Sunny Home Manager currently impossible. $attstr"; $retry = 1; - return ($reread,$retry,$login_state,$state); + return ($reread,$retry,$errstate,$state); } if($k =~ m/ErrorMessages/x && $val =~ /The current data cannot be retrieved from the PV system. Check the cabling and configuration of the following energy meters/) { ## no critic 'regular expression' # Regular expression without "/x" flag nicht anwenden !!! # Energiedaten konnten nicht ermittelt werden, Daten neu lesen mit Zeitverzögerung Log3 $name, 3, "$name - The current data cannot be retrieved from the PV system. $attstr"; $retry = 1; - return ($reread,$retry,$login_state,$state); + return ($reread,$retry,$errstate,$state); } } } @@ -2085,12 +2160,12 @@ sub analyzeData { ## no $state = ($p1 // "")." ".($p2 // ""); } - Log3 ($name, 5, "$name - No JSON Data received:\n ".$njdat) if($v5d eq "loginData"); + Log3 ($name, 5, "$name - No JSON Data received:\n ".$njdat); - $login_state = 0; + $errstate = 1; } -return ($reread,$retry,$login_state,$state); +return ($reread,$retry,$errstate,$state); } ################################################################ diff --git a/fhem/contrib/DS_Starter/76_SMAPortal.pm b/fhem/contrib/DS_Starter/76_SMAPortal.pm index 9f5ea1acb..be0cd1c2e 100644 --- a/fhem/contrib/DS_Starter/76_SMAPortal.pm +++ b/fhem/contrib/DS_Starter/76_SMAPortal.pm @@ -231,7 +231,7 @@ sub Initialize { "interval ". "showPassInLog:1,0 ". "userAgent ". - "verbose5Data:none,loginData,balanceData,liveData,weatherData,forecastData,consumerLiveData,consumerDayData,consumerMonthData,consumerYearData ". + "verbose5Data:none,loginData,balanceDayData,liveData,weatherData,forecastData,consumerLiveData,consumerDayData,consumerMonthData,consumerYearData ". $readingFnAttributes; eval { FHEM::Meta::InitMod( __FILE__, $hash ) }; ## no critic 'eval' # für Meta.pm (https://forum.fhem.de/index.php/topic,97589.0.html) @@ -756,14 +756,13 @@ sub GetSetData { ## no cri my $cookieLocation = AttrVal($name, "cookieLocation", "./log/".$name."_cookie.txt"); my $v5d = AttrVal($name, "verbose5Data", "none"); my $dl = AttrVal($name, "detailLevel", 1); # selected Detail Level - my ($ccyeardata_content) = (""); my $state = "ok"; my ($reread,$retry) = (0,0); my ($exceed,$newcycle) = (0,0); - my ($balancedataday_content,$ccdaydata_content,$ccmonthdata_content) = ("","",""); - my ($st,$lc) = ("",""); + my ($st,$lc) = ("",""); + my @da = (); + my ($ccdaydata_content,$ccmonthdata_content,$ccyeardata_content) = ("","",""); my ($d,$op,$paref); - my @da = (); if($setp ne "none") { # Verbraucher soll in den Status $op geschaltet werden @@ -858,8 +857,11 @@ sub GetSetData { ## no cri tag => "liveData" }); - $paref = [ $hash,$errstate,$state,$livedata ]; - ($reread,$retry,$errstate,$state) = analyzeData($paref); + ($reread,$retry,$errstate,$state) = analyzeData ({ hash => $hash, + errstate => $errstate, + state => $state, + data => $livedata + }); if($errstate) { $st = encode_base64 ( $state,""); @@ -912,8 +914,11 @@ sub GetSetData { ## no cri tag => "weatherData" }); - $paref = [ $hash,$errstate,$state,$weatherdata ]; - ($reread,$retry,$errstate,$state) = analyzeData($paref); + ($reread,$retry,$errstate,$state) = analyzeData ({ hash => $hash, + errstate => $errstate, + state => $state, + data => $weatherdata + }); if($errstate) { $st = encode_base64 ( $state,""); @@ -928,25 +933,29 @@ sub GetSetData { ## no cri ### Statistic Data Tag (anchorTime beachten !) - ################################################ - Log3 ($name, 4, "$name - Getting statistic day data"); - - my $req = HTTP::Request->new( 'POST', 'https://www.sunnyportal.com/FixedPages/HoManEnergyRedesign.aspx/GetLegendWithValues' ); - + ################################################ my $anchort = int ($time / 1000); # anchorTime -> abzurufendes Datum my $tab = 1; # Tab 1 -> Tag , 2->Monat, 3->Jahr, 4->Gesamt - my $cont = qq{"tabNumber":$tab,"anchorTime":$anchort}; + my %fields = ("Content-Type" => "application/json; charset=utf-8"); + my $cont = qq{ {"tabNumber":$tab,"anchorTime":$anchort} }; + + my ($balancedataday,$balancedataday_content) = _putData ({ name => $name, + ua => $ua, + call => 'https://www.sunnyportal.com/FixedPages/HoManEnergyRedesign.aspx/GetLegendWithValues', + fields => \%fields, + content => $cont, + tag => "balanceDayData" + }); + + ($reread,$retry,$errstate,$state) = analyzeData ({ hash => $hash, + errstate => $errstate, + state => $state, + data => $balancedataday + }); - $req->header ( "Content-Type" => "application/json; charset=utf-8" ); - $req->header ( "Content-Length" => 39 ); - $req->content ( "{$cont}" ); - - my $res = $ua->request( $req ); - my $balancedataday_content = $res->content; - - if($v5d eq "balanceData") { - Log3 ($name, 5, "$name - Return Code: ".$res->code); - Log3 ($name, 5, "$name - Statistic data received:\n".Dumper decode_json($balancedataday_content)); + if($errstate) { + $st = encode_base64 ( $state,""); + return "$name|0|0|$errstate|$getp|$setp|$st"; } if ($balancedataday_content && $balancedataday_content !~ m/undefined/ix) { @@ -964,8 +973,11 @@ sub GetSetData { ## no cri tag => "forecastData" }); - $paref = [ $hash,$errstate,$state,$forecastdata ]; - ($reread,$retry,$errstate,$state) = analyzeData($paref); + ($reread,$retry,$errstate,$state) = analyzeData ({ hash => $hash, + errstate => $errstate, + state => $state, + data => $forecastdata + }); if($errstate) { $st = encode_base64 ($state, ""); @@ -991,8 +1003,11 @@ sub GetSetData { ## no cri tag => "consumerLiveData" }); - $paref = [ $hash,$errstate,$state,$consumerlivedata ]; - ($reread,$retry,$errstate,$state) = analyzeData($paref); + ($reread,$retry,$errstate,$state) = analyzeData ({ hash => $hash, + errstate => $errstate, + state => $state, + data => $consumerlivedata + }); if($errstate) { $st = encode_base64 ( $state,""); @@ -1177,7 +1192,7 @@ return ($state, $errstate); } ################################################################ -# Standard Abruf Daten +# Standard Abruf Daten GET ################################################################ sub _getData { my $paref = shift; @@ -1205,6 +1220,37 @@ sub _getData { return ($data,$dcont); } +################################################################ +# Standard Abruf Daten POST +################################################################ +sub _putData { + my $paref = shift; + my $name = $paref->{name}; + my $ua = $paref->{ua}; + my $call = $paref->{call}; + my $fields = $paref->{fields}; + my $content = $paref->{content}; + my $tag = $paref->{tag}; + my $v5d = AttrVal($name, "verbose5Data", "none"); + + my $cont; + + Log3 ($name, 4, "$name - Getting $tag"); + + my $data = $ua->post( $call, %$fields, Content => $content ); + + my $dcont = $data->content; + + $cont = eval{decode_json($dcont)} or do { $cont = $dcont }; + + if($v5d eq $tag) { + Log3 ($name, 5, "$name - Return Code: ".$data->code); + Log3 ($name, 5, "$name - $tag received:\n".Dumper $cont); + } + +return ($data,$dcont); +} + ################################################################ ## Verarbeitung empfangene Daten, setzen Readings ################################################################ @@ -2044,10 +2090,10 @@ return 1; ################################################################ sub analyzeData { ## no critic 'complexity' my $paref = shift; - my $hash = $paref->[0]; - my $errstate = $paref->[1]; - my $state = $paref->[2]; - my $ad = $paref->[3]; + my $hash = $paref->{hash}; + my $errstate = $paref->{errstate}; + my $state = $paref->{state}; + my $ad = $paref->{data}; my $name = $hash->{NAME}; my ($reread,$retry) = (0,0); my $data = ""; @@ -2114,7 +2160,7 @@ sub analyzeData { ## no $state = ($p1 // "")." ".($p2 // ""); } - Log3 ($name, 5, "$name - No JSON Data received:\n ".$njdat) if($v5d eq "loginData"); + Log3 ($name, 5, "$name - No JSON Data received:\n ".$njdat); $errstate = 1; }