diff --git a/fhem/CHANGED b/fhem/CHANGED index 508fda383..fe0fc7db0 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. + - feature: 76_SMAPortal: statistics data provider of Month, Year and Total - change: 89_FULLY: Accepts port number in device definition - change: 93_DbRep: improve get <> versionNotes 2 - change: 76_SMAPortal: imptove cookie/login management diff --git a/fhem/FHEM/76_SMAPortal.pm b/fhem/FHEM/76_SMAPortal.pm index d9278d374..b9120b4ef 100644 --- a/fhem/FHEM/76_SMAPortal.pm +++ b/fhem/FHEM/76_SMAPortal.pm @@ -136,6 +136,7 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "3.2.0" => "30.06.2020 add data provider balanceCurrentData (experimental), balanceMonthData, balanceYearData ", "3.1.2" => "25.06.2020 don't delete cookie after every data retrieval, change login management ", "3.1.1" => "24.06.2020 change german Error regex, get plantOid from cookie if not in JSON ", "3.1.0" => "20.06.2020 language of SMA Portal messages depend on global language attribute, avoid order problems by ". @@ -224,28 +225,35 @@ my %statkeys = ( # Statistikdaten auszulesende Schlüs my %subs; # Arbeitskopie von %stpl my %stpl = ( # Ausgangstemplate Subfunktionen der Datenprovider - consumerMasterdata => { doit => 1, level => 'L00', func => '_getConsumerMasterdata'}, # mandatory - plantMasterData => { doit => 1, level => 'L00', func => '_getPlantMasterData' }, # mandatory - liveData => { doit => 0, level => 'L01', func => '_getLiveData' }, - weatherData => { doit => 0, level => 'L02', func => '_getWeatherData' }, - balanceDayData => { doit => 0, level => 'L03', func => '_getBalanceDayData' }, - forecastData => { doit => 0, level => 'L04', func => '_getForecastData' }, - consumerCurrentdata => { doit => 0, level => 'L05', func => '_getConsumerCurrData' }, - consumerDayData => { doit => 0, level => 'L06', func => '_getConsumerDayData' }, - consumerMonthData => { doit => 0, level => 'L07', func => '_getConsumerMonthData' }, - consumerYearData => { doit => 0, level => 'L08', func => '_getConsumerYearData' }, - plantLogbook => { doit => 0, level => 'L09', func => '_getPlantLogbook' }, + consumerMasterdata => { doit => 1, level => 'L00', func => '_getConsumerMasterdata' }, # mandatory + plantMasterData => { doit => 1, level => 'L00', func => '_getPlantMasterData' }, # mandatory + liveData => { doit => 0, level => 'L01', func => '_getLiveData' }, + weatherData => { doit => 0, level => 'L02', func => '_getWeatherData' }, + forecastData => { doit => 0, level => 'L04', func => '_getForecastData' }, + consumerCurrentdata => { doit => 0, level => 'L05', func => '_getConsumerCurrData' }, + consumerDayData => { doit => 0, level => 'L06', func => '_getConsumerDayData' }, + consumerMonthData => { doit => 0, level => 'L07', func => '_getConsumerMonthData' }, + consumerYearData => { doit => 0, level => 'L08', func => '_getConsumerYearData' }, + plantLogbook => { doit => 0, level => 'L09', func => '_getPlantLogbook' }, + balanceCurrentData => { doit => 0, level => 'L10', func => '_getBalanceCurrentData' }, + balanceDayData => { doit => 0, level => 'L11', func => '_getBalanceDayData' }, + balanceMonthData => { doit => 0, level => 'L12', func => '_getBalanceMonthData' }, + balanceYearData => { doit => 0, level => 'L13', func => '_getBalanceYearData' }, + balanceTotalData => { doit => 0, level => 'L14', func => '_getBalanceTotalData' }, ); # Tags der verfügbaren Datenquellen -my @pd = qw( balanceDayData - consumerCurrentdata - consumerMasterdata +my @pd = qw( plantMasterData + consumerMasterdata + balanceDayData + balanceMonthData + balanceYearData + balanceTotalData + consumerCurrentdata consumerDayData consumerMonthData consumerYearData forecastData liveData - plantMasterData weatherData plantLogbook ); @@ -930,8 +938,12 @@ sub GetSetData { ## no cri if($getp ne "none") { for my $k (keys %{$subs{$name}}) { - next if(!$subs{$name}{$k}{doit}); + next if(!$subs{$name}{$k}{doit}); no strict "refs"; ## no critic 'NoStrict' + if(!defined &{$subs{$name}{$k}{func}}) { + Log3 ($name, 2, qq{$name - WARNING - data provider '$k' call function '$subs{$name}{$k}{func}' doesn't exist and is ignored }); + next; + } ($errstate,$state,$reread,$retry) = &{$subs{$name}{$k}{func}} ({ name => $name, ua => $ua, state => $state, @@ -1415,6 +1427,43 @@ sub _getForecastData { ## no critic "not used" return ($errstate,$state,$reread,$retry); } +################################################################ +# Abruf Statistik Daten Day +# (anchorTime beachten !) +################################################################ +sub _getBalanceCurrentData { ## no critic "not used" + my $paref = shift; + my $name = $paref->{name}; + my $ua = $paref->{ua}; # LWP Useragent + my $state = $paref->{state}; + my $daref = $paref->{daref}; # Referenz zum Datenarray + + my ($reread,$retry,$errstate) = (0,0,0); + + 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 $anchort = int($cts + $offset); # anchorTime in UTC -> abzurufendes Datum + + my $tab = 0; # Tab 0 -> Current, Tab 1 -> Tag , 2->Monat, 3->Jahr, 4->Gesamt + my %fields = ("Content-Type" => "application/json; charset=utf-8"); + my $cont = qq{{"tabNumber":$tab,"anchorTime":$anchort}}; + + ($errstate,$state) = __dispatchPost ({ name => $name, + ua => $ua, + call => 'https://www.sunnyportal.com/FixedPages/HoManEnergyRedesign.aspx/GetLegendWithValues', + tag => "balanceCurrentData", + state => $state, + fnaref => [ qw( extractStatisticData ) ], + fields => \%fields, + content => $cont, + addon => "Current", + daref => $daref + }); + +return ($errstate,$state,$reread,$retry); +} + ################################################################ # Abruf Statistik Daten Day # (anchorTime beachten !) @@ -1435,7 +1484,7 @@ sub _getBalanceDayData { ## no critic "not used" my $tab = 1; # Tab 1 -> Tag , 2->Monat, 3->Jahr, 4->Gesamt my %fields = ("Content-Type" => "application/json; charset=utf-8"); - my $cont = qq{ {"tabNumber":$tab,"anchorTime":$anchort} }; + my $cont = qq{{"tabNumber":$tab,"anchorTime":$anchort}}; ($errstate,$state) = __dispatchPost ({ name => $name, ua => $ua, @@ -1452,6 +1501,117 @@ sub _getBalanceDayData { ## no critic "not used" return ($errstate,$state,$reread,$retry); } +################################################################ +# Abruf Statistik Daten Month +# (anchorTime beachten !) +################################################################ +sub _getBalanceMonthData { ## no critic "not used" + my $paref = shift; + my $name = $paref->{name}; + my $ua = $paref->{ua}; # LWP Useragent + my $state = $paref->{state}; + my $daref = $paref->{daref}; # Referenz zum Datenarray + + my ($reread,$retry,$errstate) = (0,0,0); + + 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 $anchort = int($cts + $offset); # anchorTime in UTC -> abzurufendes Datum + + my $tab = 2; # Tab 1 -> Tag , 2->Monat, 3->Jahr, 4->Gesamt + my %fields = ("Content-Type" => "application/json; charset=utf-8"); + my $cont = qq{{"tabNumber":$tab,"anchorTime":$anchort}}; + + ($errstate,$state) = __dispatchPost ({ name => $name, + ua => $ua, + call => 'https://www.sunnyportal.com/FixedPages/HoManEnergyRedesign.aspx/GetLegendWithValues', + tag => "balanceMonthData", + state => $state, + fnaref => [ qw( extractStatisticData ) ], + fields => \%fields, + content => $cont, + addon => "Month", + daref => $daref + }); + +return ($errstate,$state,$reread,$retry); +} + +################################################################ +# Abruf Statistik Daten Year +# (anchorTime beachten !) +################################################################ +sub _getBalanceYearData { ## no critic "not used" + my $paref = shift; + my $name = $paref->{name}; + my $ua = $paref->{ua}; # LWP Useragent + my $state = $paref->{state}; + my $daref = $paref->{daref}; # Referenz zum Datenarray + + my ($reread,$retry,$errstate) = (0,0,0); + + 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 $anchort = int($cts + $offset); # anchorTime in UTC -> abzurufendes Datum + + my $tab = 3; # Tab 1 -> Tag , 2->Monat, 3->Jahr, 4->Gesamt + my %fields = ("Content-Type" => "application/json; charset=utf-8"); + my $cont = qq{{"tabNumber":$tab,"anchorTime":$anchort}}; + + ($errstate,$state) = __dispatchPost ({ name => $name, + ua => $ua, + call => 'https://www.sunnyportal.com/FixedPages/HoManEnergyRedesign.aspx/GetLegendWithValues', + tag => "balanceYearData", + state => $state, + fnaref => [ qw( extractStatisticData ) ], + fields => \%fields, + content => $cont, + addon => "Year", + daref => $daref + }); + +return ($errstate,$state,$reread,$retry); +} + +################################################################ +# Abruf Statistik Daten Year +# (anchorTime beachten !) +################################################################ +sub _getBalanceTotalData { ## no critic "not used" + my $paref = shift; + my $name = $paref->{name}; + my $ua = $paref->{ua}; # LWP Useragent + my $state = $paref->{state}; + my $daref = $paref->{daref}; # Referenz zum Datenarray + + my ($reread,$retry,$errstate) = (0,0,0); + + 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 $anchort = int($cts + $offset); # anchorTime in UTC -> abzurufendes Datum + + my $tab = 4; # Tab 1 -> Tag , 2->Monat, 3->Jahr, 4->Gesamt + my %fields = ("Content-Type" => "application/json; charset=utf-8"); + my $cont = qq{{"tabNumber":$tab,"anchorTime":$anchort}}; + + ($errstate,$state) = __dispatchPost ({ name => $name, + ua => $ua, + call => 'https://www.sunnyportal.com/FixedPages/HoManEnergyRedesign.aspx/GetLegendWithValues', + tag => "balanceTotalData", + state => $state, + fnaref => [ qw( extractStatisticData ) ], + fields => \%fields, + content => $cont, + addon => "Total", + daref => $daref + }); + +return ($errstate,$state,$reread,$retry); +} + ################################################################ # Abruf Anlagen Logbuch ################################################################ @@ -1575,7 +1735,7 @@ sub __dispatchPost { my @func = @$fnref; no strict "refs"; ## no critic 'NoStrict' for my $fn (@func) { - &{$fn} ($hash,$daref,$data_cont,$fnaddon); + &{$fn} ($hash,$daref,$data_cont,$fnaddon,$tag); } use strict "refs"; } @@ -2173,13 +2333,14 @@ return; ################################################################ # Auswertung Statistic Daten -# $period = Day | Month | Year +# $period = Current | Day | Month | Year ################################################################ sub extractStatisticData { my $hash = shift; my $daref = shift; my $statistic = shift; my $period = shift; + my $tag = shift; my $name = $hash->{NAME}; my $sd; @@ -2188,7 +2349,7 @@ sub extractStatisticData { $statistic = eval{decode_json($statistic)} or do { Log3 ($name, 2, "$name - ERROR - can't decode JSON Data"); return; }; - my $lv = $stpl{balanceDayData}{level}; + my $lv = $stpl{$tag}{level}; if(ref $statistic eq "HASH") { $sd = decode_json ( encode('UTF-8', $statistic->{d}) ); @@ -2198,7 +2359,7 @@ sub extractStatisticData { for my $a (@$sd) { # jedes ARRAY-Element ist ein HASH my $k = $a->{Key}; my $v = $a->{Value}; - push @$daref, "${lv}_Today_${k}:$v" if(defined $statkeys{$k}); + push @$daref, "${lv}_${period}_${k}:$v" if(defined $statkeys{$k}); } } @@ -3805,13 +3966,16 @@ return; liveData - generates readings of the current generation and consumption data weatherData - Weather data offered by SMA are retrieved - balanceDayData - Statistics data of the day are retrieved forecastData - Forecast data of generation/consumption and consumer planning data are generated consumerCurrentdata - current consumer data are generated consumerDayData - consumer data day are generated consumerMonthData - consumer data month are generated consumerYearData - consumer data year are generated plantLogbook - the maximum of 25 most recent entries of the plant logbook are retrieved + balanceDayData - Statistics data of the day are retrieved + balanceMonthData - Statistics data of the month are retrieved + balanceYearData - Statistical data of the year are retrieved + balanceTotalData - Total statistics data are retrieved
@@ -4049,13 +4213,16 @@ return; liveData - erzeugt Readings der aktuellen Erzeugungs- und Verbrauchsdaten weatherData - von SMA angebotene Wetterdaten werden abgerufen - balanceDayData - Statistikdaten des Tages werden abgerufen forecastData - Vorhersagedaten der Erzeugung / Verbrauch und Verbraucherplanungsdaten werden erzeugt consumerCurrentdata - aktuelle Verbraucherdaten werden erzeugt consumerDayData - Verbraucherdaten Tag werden erzeugt consumerMonthData - Verbraucherdaten Monat werden erzeugt consumerYearData - Verbraucherdaten Jahr werden erzeugt plantLogbook - die maximal 25 aktuellsten Einträge des Anlagenlogbuchs werden abgerufen + balanceDayData - Statistikdaten des Tages werden abgerufen + balanceMonthData - Statistikdaten des Monats werden abgerufen + balanceYearData - Statistikdaten des Jahres werden abgerufen + balanceTotalData - Statistikdaten Gesamt werden abgerufen