2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-03-03 16:56:54 +00:00

76_SMAPortal: contrib 2.10.0

git-svn-id: https://svn.fhem.de/fhem/trunk@22101 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
nasseeder1 2020-06-03 13:33:11 +00:00
parent 5465f042d3
commit d5d226b682

View File

@ -121,7 +121,7 @@ BEGIN {
# my $pkg = caller(0);
# my $main = $pkg;
# $main =~ s/^(?:.+::)?([^:]+)$/main::$1\_/gx;
# foreach (@_) {
# for (@_) {
# *{ $main . $_ } = *{ $pkg . '::' . $_ };
# }
GP_Export(
@ -134,6 +134,7 @@ BEGIN {
# Versions History intern
my %vNotesIntern = (
"2.10.0" => "03.06.2020 refactoring login process ",
"2.9.0" => "01.06.2020 add get today statistic data ",
"2.8.1" => "31.05.2020 attribute timeout, maxCallCycle deleted ",
"2.8.0" => "31.05.2020 refactoring process logic, attribute cookielifetime & getDataRetries deleted, command delCookieFile deleted ".
@ -193,6 +194,22 @@ my $sleepretry = 0.5; # Sleep zwischen Data Call Retries (o
my $sleepexc = 2; # Sleep vor neuem Datencall nach Überschreitung Threshold (Data Calls mit gleichem Cookie)
my $defmaxcycles = 19; # Standard max. Anzahl Datenabrufzyklen abgeleitet von Interval 120 (wird bei Automatic berechnet)
my %statkeys = ( # Statistikdaten auszulesende Schlüssel
Energy => 1,
FeedIn => 1,
GridConsumption => 1,
SelfConsumption => 1,
SelfSupply => 1,
DirectConsumption => 1,
TotalConsumption => 1,
BackupOut => 1,
BackupIn => 1,
SelfConsumptionRate => 1,
DirectConsumptionRate => 1,
AutarkyRate => 1,
);
###############################################################
# SMAPortal Initialize
###############################################################
@ -212,7 +229,7 @@ sub Initialize {
"interval ".
"showPassInLog:1,0 ".
"userAgent ".
"verbose5Data:none,balanceData,liveData,weatherData,forecastData,consumerLiveData,consumerDayData,consumerMonthData,consumerYearData ".
"verbose5Data:none,loginData,balanceData,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)
@ -295,7 +312,7 @@ sub Set { ## no critic
;
if($hash->{HELPER}{PLANTOID} && $hash->{HELPER}{CONSUMER}) {
my $lfd = 0;
foreach my $key (keys %{$hash->{HELPER}{CONSUMER}{$lfd}}) {
for my $key (keys %{$hash->{HELPER}{CONSUMER}{$lfd}}) {
my $dev = $hash->{HELPER}{CONSUMER}{$lfd}{DeviceName};
if($dev) {
$ad .= "|" if($lfd != 0);
@ -608,7 +625,7 @@ sub Attr {
unless ($aVal =~ /^\d+$/x) {return " The Value for $aName is not valid. Use only figures 0-9 !";}
}
if($aName =~ m/interval/x) {
return qq{The interval must be >= 30 seconds or 0 if you don't want use automatic updates} if($aVal > 0 && $aVal < 30);
return qq{The interval must be >= 120 seconds or 0 if you don't want use automatic updates} if($aVal > 0 && $aVal < 30);
InternalTimer(gettimeofday()+1.0, "FHEM::SMAPortal::CallInfo", $hash, 0);
}
}
@ -741,8 +758,9 @@ sub GetSetData { ## no cri
my ($reread,$retry) = (0,0);
my ($exceed,$newcycle) = (0,0);
my ($forecast_content,$weatherdata_content,$consumerlivedata_content) = ("","","");
my ($balancedataday_content,$ccdaydata_content,$ccmonthdata_content) = ("","","");
my ($livedata_content,$d,$op);
my ($balancedataday_content,$ccdaydata_content,$ccmonthdata_content,$livedata_content) = ("","","","");
my ($st,$bcd,$lc,$fc,$wc,$cl,$cd,$cm,$cy) = ("","","","","","","","","");
my ($d,$op);
if($setp ne "none") {
# Verbraucher soll in den Status $op geschaltet werden
@ -757,7 +775,7 @@ sub GetSetData { ## no cri
# $ua->add_handler( request_send => sub { shift->dump; return } ); # for debugging
# $ua->add_handler( response_done => sub { shift->dump; return } );
# Header Daten
# Default Header Daten
$ua->default_header("Accept" => "*/*",
"Accept-Encoding" => "gzip, deflate, br",
"Accept-Language" => "en-US;q=0.7,en;q=0.3",
@ -783,57 +801,104 @@ sub GetSetData { ## no cri
my $offset = fhemTzOffset($cts);
my $time = int(($cts + $offset) * 1000); # add Timestamp in Millisekunden and UTC
### Live-Daten abrufen
#########################
my $livedata = $ua->get( 'https://www.sunnyportal.com/homemanager?t='.$time ); # V2.6.2
if(($livedata->content =~ m/FeedIn/ix) && ($livedata->content !~ m/expired/ix)) {
Log3 $name, 4, "$name - Login to SMA-Portal successful";
### Login
##############
my $loginp = $ua->post('https://www.sunnyportal.com/Templates/Start.aspx');
my $retcode = $loginp->code;
my $location = $loginp->header('Location') // "";
$livedata_content = $livedata->content; # JSON Live Daten
$login_state = 1;
Log3 ($name, 4, "$name - Getting live data");
Log3 ($name, 5, "$name - Data received:\n".Dumper decode_json($livedata_content)) if($v5d eq "liveData");
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));
}
} else {
my $usernameField = "ctl00\$ContentPlaceHolder1\$Logincontrol1\$txtUserName";
my $passwordField = "ctl00\$ContentPlaceHolder1\$Logincontrol1\$txtPassword";
my $loginField = "__EVENTTARGET";
my $loginButton = "ctl00\$ContentPlaceHolder1\$Logincontrol1\$LoginBtn";
$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, "$name - Credentials couldn't be retrieved successfully - make sure you've set it with \"set $name credentials <username> <password>\"");
Log3($name, 1, qq{$name - Credentials couldn't be retrieved successfully - make sure you've set it with "set $name credentials <username> <password>"});
$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";
my $loginp = $ua->post('https://www.sunnyportal.com/Templates/Start.aspx',[$usernameField => $username, $passwordField => $password, "__EVENTTARGET" => $loginButton]);
$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') // "";
Log3 ($name, 5, "$name - Login-Page return code: ".$loginp->code);
Log3 ($name, 5, "$name - Login-Page return: ".$loginp->content);
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($loginp->content =~ /Logincontrol1_ErrorLabel/ix) {
Log3 ($name, 1, "$name - Error: login to SMA-Portal failed");
$livedata_content = "{\"Login-Status\":\"failed\"}";
} else {
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
$livedata_content = '{"Login-Status":"successful", "InfoMessages":["login to SMA-Portal successful."]}';
BlockingInformParent("FHEM::SMAPortal::setFromBlocking", [$name, "NULL", "NULL", (gettimeofday())[0], "NULL", "NULL"], 0);
}
$login_state = 1;
my $shmp = $ua->get('https://www.sunnyportal.com/FixedPages/HoManLive.aspx');
Log3 ($name, 5, "$name - ".$shmp->code);
} else {
Log3 ($name, 2, "$name - ERROR - Login into SMA-Portal failed !");
$state = "login failed - check user and password";
$login_state = 0;
}
}
}
($reread,$retry) = analyzeData($hash,$livedata_content);
} 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) {
$st = encode_base64 ( $state,"");
return "$name|0|0|$login_state|$getp|$setp|$st";
}
### Live-Daten abrufen
#########################
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);
if(!$login_state) {
$st = encode_base64 ( $state,"");
return "$name|0|0|$login_state|$getp|$setp|$st";
}
goto &GetSetData if($reread);
@ -864,11 +929,12 @@ sub GetSetData { ## no cri
}
### Verbraucher schalten
#######################################
if($setp ne "none") {
my ($serial,$id);
foreach my $key (keys %{$hash->{HELPER}{CONSUMER}}) {
for my $key (keys %{$hash->{HELPER}{CONSUMER}}) {
my $h = $hash->{HELPER}{CONSUMER}{$key}{DeviceName};
if($h && $h eq $d) {
$serial = $hash->{HELPER}{CONSUMER}{$key}{SerialNumber};
@ -890,7 +956,7 @@ sub GetSetData { ## no cri
$state = "ok - switched consumer $d to $op";
BlockingInformParent("FHEM::SMAPortal::setFromBlocking", [$name, "all", "none", "NULL", "NULL", "NULL"], 0);
} else {
$state = "Error - couldn't switched consumer $d to $op";
$state = "Error - couldn't switch consumer $d to $op";
}
}
@ -902,13 +968,19 @@ sub GetSetData { ## no cri
# JSON Wetterdaten
Log3 ($name, 4, "$name - Getting weather data");
my $weatherdata = $ua->get('https://www.sunnyportal.com/Dashboard/Weather');
$weatherdata_content = $weatherdata->content;
Log3 ($name, 5, "$name - Data received:\n".Dumper decode_json($weatherdata_content)) if($v5d eq "weatherData");
if($v5d eq "weatherData") {
Log3 ($name, 5, "$name - Return Code: ".$weatherdata->code);
Log3 ($name, 5, "$name - Data received:\n".Dumper decode_json($weatherdata_content));
}
# 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' );
my $anchort = int ($time / 1000); # anchorTime -> abzurufendes Datum
@ -922,7 +994,10 @@ sub GetSetData { ## no cri
my $res = $ua->request( $req );
$balancedataday_content = $res->content;
Log3 ($name, 5, "$name - Data received:\n".Dumper decode_json($balancedataday_content)) if($v5d eq "balanceData");
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));
}
# JSON Forecast Daten
@ -930,11 +1005,11 @@ sub GetSetData { ## no cri
Log3 ($name, 4, "$name - Getting forecast data");
my $forecast_page = $ua->get('https://www.sunnyportal.com/HoMan/Forecast/LoadRecommendationData');
Log3 ($name, 5, "$name - Return Code: ".$forecast_page->code) if($v5d eq "forecastData");
if ($forecast_page->content =~ m/ForecastChartDataPoint/ix) {
$forecast_content = $forecast_page->content;
Log3 ($name, 5, "$name - Forecast data received:\n".Dumper decode_json($forecast_content)) if($v5d eq "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));
}
}
@ -943,12 +1018,11 @@ sub GetSetData { ## no cri
Log3 ($name, 4, "$name - Getting consumer live data");
my $consumerlivedata = $ua->get('https://www.sunnyportal.com/Homan/ConsumerBalance/GetLiveProxyValues');
Log3 ($name, 5, "$name - Return Code: ".$consumerlivedata->code) if($v5d eq "consumerLiveData");
if ($consumerlivedata->content =~ m/HoManConsumerLiveData/ix) {
$consumerlivedata_content = $consumerlivedata->content;
Log3 ($name, 5, "$name - Consumer live data received:\n".Dumper decode_json($consumerlivedata_content)) if($v5d eq "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($hash->{HELPER}{PLANTOID}) {
@ -1014,7 +1088,6 @@ sub GetSetData { ## no cri
}
# Daten müssen als Einzeiler zurückgegeben werden
my ($st,$bcd,$lc,$fc,$wc,$cl,$cd,$cm,$cy) = ("","","","","","","","","");
$st = encode_base64 ( $state,"");
$bcd = encode_base64 ( $balancedataday_content, "" ) if($balancedataday_content);
$lc = encode_base64 ( $livedata_content, "" ) if($livedata_content);
@ -1190,29 +1263,29 @@ sub ParseData { ## no critic
readingsDelete($hash,"L1_WarningMessages") if(!$warnMsg);
readingsDelete($hash,"L1_InfoMessages") if(!$infoMsg);
if ($forecast_content && $forecast_content !~ m/undefined/ix) {
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) {
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) {
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) {
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) {
if ($ccyeardata_content && $ccyeardata_content !~ m/undefined/ix && $dl > 2) {
# Auswertung Consumer Energiedaten des aktuellen Jahres
extractConsumerHistData ($hash,$ccyeardata_content,"year");
}
@ -1232,25 +1305,28 @@ sub ParseData { ## no critic
my $gc = ReadingsNum($name, "L1_GridConsumption", 0);
my $sum = $fi-$gc;
if(!$pv && !$fi && !$gc) {
if($login_state && !$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");
} else {
$hash->{HELPER}{LASTLDSUCCTIME} = FmtDateTime(time()) if($getp ne "none");
}
readingsBeginUpdate($hash);
if($login_state) {
if($setp ne "none") {
my ($d,$op) = split(":",$setp);
$op = ($op eq "auto")?"off (automatic)":$op;
readingsBulkUpdate($hash, "L3_${d}_Switch", $op);
}
readingsBulkUpdate($hash, "state" , $state);
readingsBulkUpdate($hash, "lastCycleTime", $ctime);
readingsBulkUpdate($hash, "summary" , "$sum W");
} else {
readingsBulkUpdate($hash, "L1_Login-Status", "failed");
}
readingsBulkUpdate($hash, "state", $state);
readingsEndUpdate($hash, 1);
delcookiefile ($hash);
@ -1310,10 +1386,6 @@ sub extractForecastData { ## no critic
my $dl = AttrVal($name, "detailLevel", 1);
if($dl <= 1) {
return;
}
Log3 ($name, 4, "$name - ##### extracting forecast data #### ");
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
@ -1353,7 +1425,7 @@ sub extractForecastData { ## no critic
# Loop through all forecast objects
# Energie wird als "J" geliefert, Wh = J / 3600
foreach my $fc_obj (@{$forecast->{'ForecastSeries'}}) {
for my $fc_obj (@{$forecast->{'ForecastSeries'}}) {
my $fc_datetime = $fc_obj->{'TimeStamp'}->{'DateTime'}; # Example for DateTime: 2016-02-15T23:00:00
my $tkind = $fc_obj->{'TimeStamp'}->{'Kind'}; # Zeitart: Unspecified, Utc
@ -1458,53 +1530,36 @@ return;
sub extractWeatherData {
my ($hash,$weather) = @_;
my $name = $hash->{NAME};
my ($tsymbol,$ttoday,$ttomorrow);
my @da = ();
Log3 ($name, 4, "$name - ##### extracting weather data #### ");
for my $k (keys %$weather) {
next if(!$k);
Log3 ($name, 4, qq{$name - Weatherdata content "$k": }.Dumper $weather->{$k});
if (ref $weather->{$k} eq "HASH") {
my $ih = $weather->{$k};
my $day = $k;
my $symbol = encode("utf8", $weather->{$k}{TemperatureSymbol});
my $temp = sprintf("%.1f", $weather->{$k}{Temperature});
my $wdesc = $weather->{$k}{WeatherDescription};
$wdesc =~ s/t/T/x;
$day =~ s/t/T/x;
push @da, "L1_${day}_Temperature:$temp $symbol";
push @da, "L1_${day}_WeatherDescription:$wdesc";
}
}
readingsBeginUpdate($hash);
for my $k (keys %$weather) {
my $new_val = "";
if (defined $weather->{$k}) {
Log3 $name, 4, "$name - Weatherdata content \"$k\": ".($weather->{$k});
if ($weather->{$k} =~ m/HASH/ix) {
my $ih = $weather->{$k};
for my $i (keys %$ih) {
my $hd0 = $weather->{$k}{$i};
if(!$hd0) {
next;
for my $elem (@da) {
my ($rn,$rval) = split ":", $elem;
readingsBulkUpdate($hash, $rn, $rval);
}
chomp $hd0;
$hd0 =~ s/[;']//gx;
$hd0 = ($hd0 =~ /^undef$/x)?"none":$hd0;
$hd0 = encode("utf8", $hd0);
Log3 $name, 4, "$name - Weatherdata \"$k $i\": $hd0";
next if($i =~ /^WeatherIcon$/x);
$new_val = $hd0;
if ($new_val) {
if($i =~ /^TemperatureSymbol$/x) {
$tsymbol = $new_val;
next;
}
if($i =~ /^Temperature$/x) {
$ttoday = sprintf("%.1f",$new_val) if($k =~ /^today$/x);
$ttomorrow = sprintf("%.1f",$new_val) if($k =~ /^tomorrow$/x);
next;
}
$k =~ s/t/T/x if($i =~ /^WeatherDescription$/x);
Log3 ($name, 4, "$name - ${k}_${i} - $new_val");
readingsBulkUpdate($hash, "L1_${k}_${i}", $new_val);
}
}
}
}
}
readingsBulkUpdate($hash, "L1_Today_Temperature", "$ttoday $tsymbol") if($ttoday && $tsymbol);
readingsBulkUpdate($hash, "L1_Tomorrow_Temperature", "$ttomorrow $tsymbol") if($ttomorrow && $tsymbol);
readingsEndUpdate($hash, 1);
@ -1524,21 +1579,6 @@ sub extractStatisticData {
Log3 ($name, 4, "$name - ##### extracting statistic data #### ");
my %ch = ( # Zieldatenhash
Energy => 1,
FeedIn => 1,
GridConsumption => 1,
SelfConsumption => 1,
SelfSupply => 1,
DirectConsumption => 1,
TotalConsumption => 1,
BackupOut => 1,
BackupIn => 1,
SelfConsumptionRate => 1,
DirectConsumptionRate => 1,
AutarkyRate => 1,
);
if(ref $statistic eq "HASH") {
$sd = decode_json ($statistic->{d});
}
@ -1547,7 +1587,7 @@ sub extractStatisticData {
for my $a (@$sd) { # jedes ARRAY-Element ist ein HASH
my $k = $a->{Key};
my $v = $a->{Value};
push @da, "$k:$v" if(defined $ch{$k});
push @da, "L1_Today_${k}:$v" if(defined $statkeys{$k});
}
}
@ -1555,7 +1595,7 @@ sub extractStatisticData {
for my $elem (@da) {
my ($rn,$rval) = split ":", $elem;
readingsBulkUpdate($hash, "L1_Today_".$rn, $rval);
readingsBulkUpdate($hash, $rn, $rval);
}
readingsEndUpdate($hash, 1);
@ -1571,17 +1611,12 @@ sub extractPlantData {
my $name = $hash->{NAME};
my ($amount,$unit);
my $dl = AttrVal($name, "detailLevel", 1);
if($dl <= 1) {
return;
}
Log3 ($name, 4, "$name - ##### extracting plant data #### ");
readingsBeginUpdate($hash);
my $ppp = $forecast->{'PlantPeakPower'};
if($ppp && $dl >= 2) {
if($ppp) {
$amount = $forecast->{'PlantPeakPower'}{'Amount'};
$unit = $forecast->{'PlantPeakPower'}{'StandardUnit'}{'Symbol'};
Log3 $name, 4, "$name - Plantdata \"PlantPeakPower Amount\": $amount";
@ -1605,9 +1640,6 @@ sub extractConsumerData {
my ($key,$val);
my $dl = AttrVal($name, "detailLevel", 1);
if($dl <= 1) {
return;
}
Log3 ($name, 4, "$name - ##### extracting consumer data #### ");
@ -1615,7 +1647,7 @@ sub extractConsumerData {
# Schleife über alle Consumer Objekte
my $i = 0;
foreach my $c (@{$forecast->{'Consumers'}}) {
for my $c (@{$forecast->{'Consumers'}}) {
$consumers{"${i}_ConsumerName"} = encode("utf8", $c->{'ConsumerName'} );
$consumers{"${i}_ConsumerOid"} = $c->{'ConsumerOid'};
$i++;
@ -1624,13 +1656,13 @@ sub extractConsumerData {
if(%consumers && $forecast->{'ForecastTimeframes'}) {
# es sind Vorhersagen zu geplanten Verbraucherschaltzeiten vorhanden
# TimeFrameStart/End Kind: "Utc"
foreach my $c (@{$forecast->{'ForecastTimeframes'}{'PlannedTimeFrames'}}) {
for my $c (@{$forecast->{'ForecastTimeframes'}{'PlannedTimeFrames'}}) {
my $tkind = $c->{'TimeFrameStart'}->{'Kind'}; # Zeitart: Unspecified, Utc
my $deviceOid = $c->{'DeviceOid'};
my $timeFrameStart = TimeAdjust($hash,$c->{'TimeFrameStart'}{'DateTime'},$tkind); # wandele UTC
my $timeFrameEnd = TimeAdjust($hash,$c->{'TimeFrameEnd'}{'DateTime'},$tkind); # wandele UTC
my $tz = $c->{'TimeFrameStart'}{'Kind'};
foreach my $k (keys(%consumers)) {
for my $k (keys(%consumers)) {
$val = $consumers{$k};
if($val eq $deviceOid && $k =~ /^(\d+)_.*$/x) {
my $lfn = $1;
@ -1642,7 +1674,7 @@ sub extractConsumerData {
}
if(%consumers) {
foreach my $key (keys(%consumers)) {
for my $key (keys(%consumers)) {
Log3 $name, 4, "$name - Consumer data \"$key\": ".$consumers{$key};
if($key =~ /ConsumerName/x && $key =~ /^(\d+)_.*$/x && $dl >= 3) {
my $lfn = $1;
@ -1684,18 +1716,13 @@ sub extractConsumerLiveData {
my %consumers;
my ($key,$val,$i,$res);
my $dl = AttrVal($name, "detailLevel", 1);
if($dl <= 2) {
return;
}
Log3 ($name, 4, "$name - ##### extracting consumer live data #### ");
readingsBeginUpdate($hash);
# allen Consumer Objekte die ID zuordnen
$i = 0;
foreach my $c (@{$clivedata->{'MeasurementData'}}) {
for my $c (@{$clivedata->{'MeasurementData'}}) {
$consumers{"${i}_ConsumerName"} = encode("utf8", $c->{'DeviceName'} );
$consumers{"${i}_ConsumerOid"} = $c->{'Consume'}{'ConsumerOid'};
$consumers{"${i}_ConsumerLfd"} = $i;
@ -1718,7 +1745,7 @@ sub extractConsumerLiveData {
# es sind Daten zu den Verbrauchern vorhanden
# Kind: "Utc" ?
$i = 0;
foreach my $c (@{$clivedata->{'ParameterData'}}) {
for my $c (@{$clivedata->{'ParameterData'}}) {
my $tkind = $c->{'Parameters'}[0]{'Timestamp'}{'Kind'}; # Zeitart: Unspecified, Utc
my $GriSwStt = $c->{'Parameters'}[0]{'Value'}; # on: 1, off: 0
my $GriSwAuto = $c->{'Parameters'}[1]{'Value'}; # automatic = 1
@ -1760,11 +1787,6 @@ sub extractConsumerHistData { #
my %consumers;
my ($key,$val,$i,$res,$gcr,$gct,$pcr,$pct,$tct,$bcr,$bct);
my $dl = AttrVal($name, "detailLevel", 1);
if($dl <= 2) {
return;
}
Log3 ($name, 4, "$name - ##### extracting consumer history data #### ");
my $bataval = (defined(ReadingsNum($name,"L1_BatteryIn", undef)) || defined(ReadingsNum($name,"L1_BatteryOut", undef)))?1:0; # Identifikation ist Battery vorhanden ?
@ -1773,7 +1795,7 @@ sub extractConsumerHistData { #
# allen Consumer Objekte die ID zuordnen
$i = 0;
foreach my $c (@{$chdata->{'Consumers'}}) {
for my $c (@{$chdata->{'Consumers'}}) {
$consumers{"${i}_ConsumerName"} = encode("utf8", $c->{'DeviceName'} );
$consumers{"${i}_ConsumerOid"} = $c->{'ConsumerOid'};
$consumers{"${i}_ConsumerLfd"} = $i;
@ -1887,8 +1909,8 @@ sub delread {
if($dl) {
# Readings ab dem angegebenen Detail-Level löschen
foreach my $key(@allrds) {
$key =~ m/^L(\d)_.*$/x;
for my $key(@allrds) {
$key =~ m/^L(\d)_/x;
if($1 && $1 >= $dl) {
delete($defs{$name}{READINGS}{$key});
}
@ -1896,7 +1918,7 @@ sub delread {
return;
}
foreach my $key(@allrds) {
for my $key(@allrds) {
delete($defs{$name}{READINGS}{$key}) if($key ne "state");
}
@ -1964,66 +1986,79 @@ return 1;
# analysiere abgerufene Daten
################################################################
sub analyzeData { ## no critic 'complexity'
my ($hash,$lc) = @_;
my ($hash,$login_state,$state,$ad) = @_;
my $name = $hash->{NAME};
my ($reread,$retry) = (0,0);
my $data = "";
my $v5d = AttrVal($name, "verbose5Data", "none");
my $ad_content = encode("utf8", $ad->decoded_content);
my $act = $hash->{HELPER}{RETRIES}; # Index aktueller Wiederholungsversuch
my $attstr = "Attempts read data again ... ($act of $maxretries)";
my $attstr = "Attempts read data again ... ($act of $maxretries)"; # Log vorbereiten
my $livedata_content = decode_json($lc);
for my $k (keys %$livedata_content) {
$data = eval{decode_json($ad_content)} or do { $data = $ad_content };
if (ref $data eq "HASH") {
for my $k (keys %$data) {
my $new_val = "";
if (defined $livedata_content->{$k}) {
if (($livedata_content->{$k} =~ m/ARRAY/ix) || ($livedata_content->{$k} =~ m/HASH/ix)) {
if($livedata_content->{$k} =~ m/ARRAY/ix) {
my $hd0 = Dumper($livedata_content->{$k}[0]);
if(!$hd0) {
next;
}
chomp $hd0;
$hd0 =~ s/[;']//gx;
$hd0 = ($hd0 =~ /^undef$/x)?"none":$hd0;
$new_val = $hd0;
if (defined $data->{$k}) {
if (($data->{$k} =~ m/ARRAY/ix) || ($data->{$k} =~ m/HASH/ix)) {
if($data->{$k} =~ m/ARRAY/ix) {
my $val = Dumper($data->{$k}[0]);
next if(!$val);
chomp $val;
$val =~ s/[;']//gx;
$val = ($val =~ /^undef$/x)? "none" : $val;
$new_val = $val;
}
} else {
$new_val = $livedata_content->{$k};
$new_val = $data->{$k};
}
if ($new_val && $k !~ /__type/ix) {
if($k =~ m/InfoMessages/x && $new_val =~ /login to SMA-Portal successful/) { ## no critic 'regular expression' # Regular expression without "/x" flag nicht anwenden !!!
# Login war erfolgreich, Daten neu lesen
Log3 $name, 3, "$name - Read portal data ...";
$reread = 1;
}
if($k =~ m/WarningMessages/x && $new_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);
return ($reread,$retry,$login_state,$state);
}
if($k =~ m/WarningMessages/x && $new_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);
return ($reread,$retry,$login_state,$state);
}
if($k =~ m/ErrorMessages/x && $new_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);
return ($reread,$retry,$login_state,$state);
}
if($k =~ m/ErrorMessages/x && $new_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 - Live data can't be retrieved. $attstr";
$retry = 1;
return ($reread,$retry);
return ($reread,$retry,$login_state,$state);
}
}
}
}
return ($reread,$retry);
} else {
my $njdat = $ad->as_string;
if($njdat =~ /401\s-\sUnauthorized/x) {
Log3 ($name, 2, "$name - ERROR - User logged in but unauthorized");
my($p1,$p2) = $njdat =~ /<h2>401\s-\sUnauthorized:.(.*)?<\/h2>.*?<h3>(.*)?<\/h3>/sx;
$state = ($p1 // "")." ".($p2 // "");
}
Log3 ($name, 5, "$name - No JSON Data received:\n ".$njdat) if($v5d eq "loginData");
$login_state = 0;
}
return ($reread,$retry,$login_state,$state);
}
################################################################
@ -2134,7 +2169,7 @@ sub PortalAsHtml {
# Verbraucherlegende und Steuerung
if ($legend) {
foreach (@pgCDev) {
for (@pgCDev) {
my($txt,$im) = split(':',$_); # $txt ist der Verbrauchername
my $cmdon = "\"FW_cmd('$FW_ME$FW_subdir?XHR=1&cmd=set $name $txt on')\"";
my $cmdoff = "\"FW_cmd('$FW_ME$FW_subdir?XHR=1&cmd=set $name $txt off')\"";
@ -2302,7 +2337,7 @@ sub PortalAsHtml {
###########################################################
# get consumer list and display it in portalGraphics
foreach (@pgCDev) {
for (@pgCDev) {
my ($itemName, undef) = split(':',$_);
$itemName =~ s/^\s+|\s+$//gx; #trim it, if blanks were used
$_ =~ s/^\s+|\s+$//gx; #trim it, if blanks were used
@ -2691,7 +2726,7 @@ sub consinject {
my $name = $hash->{NAME};
my $ret = "";
foreach (@pgCDev) {
for (@pgCDev) {
if ($_) {
my ($cons,$im,$start,$end) = split (':', $_);
Log3($name, 4, "$name - Consumer to show -> $cons, relative to current time -> start: $start, end: $end") if($i<1);
@ -2849,7 +2884,7 @@ sub SPGRefresh {
if($pload && ($hash->{HELPER}{SPGROOM} && !$hash->{HELPER}{SPGDETAIL} && !$fpr)) {
# trifft zu wenn in einer Raumansicht
my @rooms = split(",",$hash->{HELPER}{SPGROOM});
foreach (@rooms) {
for (@rooms) {
my $room = $_;
{ map { FW_directNotify("FILTER=room=$room", "#FHEMWEB:$_", "location.reload('true')", "") } devspec2array("TYPE=FHEMWEB") }
}
@ -2866,7 +2901,7 @@ sub SPGRefresh {
# parentState des SMAPortalSPG-Device updaten
my @spgs = devspec2array("TYPE=SMAPortalSPG");
my $st = ReadingsVal($name, "state", "initialized");
foreach(@spgs) {
for(@spgs) {
if($defs{$_}{PARENT} eq $name) {
next if(IsDisabled($defs{$_}{NAME}));
readingsBeginUpdate($defs{$_});