1
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-05-07 22:29:19 +00:00

76_SMAPortal: contrib 3.7.0

git-svn-id: https://svn.fhem.de/fhem/trunk@23170 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
nasseeder1 2020-11-17 16:48:00 +00:00
parent bddec907e1
commit fe39cf5130

View File

@ -49,11 +49,10 @@ use LWP::UserAgent;
use HTTP::Cookies; use HTTP::Cookies;
use JSON qw(decode_json); use JSON qw(decode_json);
use MIME::Base64; use MIME::Base64;
use Color;
use Encode; use Encode;
use utf8; use utf8;
use HttpUtils;
# Run before module compilation # Run before module compilation
BEGIN { BEGIN {
# Import from main:: # Import from main::
@ -96,7 +95,6 @@ BEGIN {
Log3 Log3
makeReadingName makeReadingName
modules modules
parseParams
readingsSingleUpdate readingsSingleUpdate
readingsBulkUpdate readingsBulkUpdate
readingsBulkUpdateIfChanged readingsBulkUpdateIfChanged
@ -120,7 +118,6 @@ BEGIN {
FW_room FW_room
FW_detail FW_detail
FW_wname FW_wname
urlEncode
) )
); );
@ -141,7 +138,8 @@ BEGIN {
# Versions History intern # Versions History intern
my %vNotesIntern = ( my %vNotesIntern = (
"9.9.9" => "12.11.2020 Studieb Kamik ", "3.7.0" => "16.11.2020 add new consumer management for switched sockets and EVCharger ",
"3.6.5" => "12.11.2020 verbose5data switchConsumer, more preselected user agents ",
"3.6.4" => "11.11.2020 preselect the user agent randomly, set min. interval to 180 s ", "3.6.4" => "11.11.2020 preselect the user agent randomly, set min. interval to 180 s ",
"3.6.3" => "05.11.2020 fix only four consumer are shown in set command drop down list ", "3.6.3" => "05.11.2020 fix only four consumer are shown in set command drop down list ",
"3.6.2" => "03.11.2020 new function _detailViewOn to Switch the detail view on SMA energy balance site, new default userAgent ", "3.6.2" => "03.11.2020 new function _detailViewOn to Switch the detail view on SMA energy balance site, new default userAgent ",
@ -269,7 +267,7 @@ my %stpl = (
balanceTotalData => { doit => 0, nohm => 0, level => 'L14', func => '_getBalanceTotalData' }, balanceTotalData => { doit => 0, nohm => 0, level => 'L14', func => '_getBalanceTotalData' },
); );
my %hua = ( # mögliche UserAgents für eine Round-Robin-Liste my %hua = ( # mögliche Random UserAgents
1 => "Mozilla/5.0 (Windows NT 10.0; rv:81.0) Gecko/20100101 Firefox/81.0", 1 => "Mozilla/5.0 (Windows NT 10.0; rv:81.0) Gecko/20100101 Firefox/81.0",
2 => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.195 Safari/537.36", 2 => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.195 Safari/537.36",
3 => "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36", 3 => "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36",
@ -279,10 +277,21 @@ my %hua = (
7 => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:86.0) Gecko/20100101 Firefox/86.0", 7 => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:86.0) Gecko/20100101 Firefox/86.0",
8 => "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:86.0) Gecko/20100101 Firefox/86.0", 8 => "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:86.0) Gecko/20100101 Firefox/86.0",
9 => "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0", 9 => "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0",
10 => "Mozilla/5.0 (Linux; Android 8.0.0; PRA-LX1 Build/HUAWEIPRA-LX1; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/86.0.4240.198 Mobile Safari/537.36" 10 => "Mozilla/5.0 (Linux; Android 8.0.0; PRA-LX1 Build/HUAWEIPRA-LX1; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/86.0.4240.198 Mobile Safari/537.36",
11 => "Mozilla/5.0 (Linux; Android 8.0.0; BAH2-L09 Build/HUAWEIBAH2-L09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/86.0.4240.185 Safari/537.36",
); );
# Tags der verfügbaren Datenquellen my %hal = ( # Header Accept-Language sprachenabhängig
"DE" => "de,en-US;q=0.7,en;q=0.3",
"EN" => "en-US;q=0.7,en;q=0.3"
);
my %hsusyid = ( # Schalten/Management der Verbraucher entspr. ihrer SUSyID
191 => { arg => ":on,off,auto", fn => \&_switchConsumer }, # 191 = Schaltdosen
315 => { arg => ":colorpicker,CT,0,1,100", fn => \&_manageConsumerByEnergy }, # 315 = SMA EV Charger
);
# Tags der verfügbaren Datenquellen
my @pd = qw( plantMasterData my @pd = qw( plantMasterData
consumerMasterdata consumerMasterdata
balanceDayData balanceDayData
@ -333,7 +342,7 @@ sub Initialize {
"showPassInLog:1,0 ". "showPassInLog:1,0 ".
"userAgent ". "userAgent ".
"useRelativeNames:1,0 ". "useRelativeNames:1,0 ".
"verbose5Data:multiple-strict,none,loginData,detailViewSwitch,".$v5d." ". "verbose5Data:multiple-strict,none,loginData,detailViewSwitch,switchConsumer,".$v5d." ".
$readingFnAttributes; $readingFnAttributes;
eval { FHEM::Meta::InitMod( __FILE__, $hash ) }; ## no critic 'eval' # für Meta.pm (https://forum.fhem.de/index.php/topic,97589.0.html) eval { FHEM::Meta::InitMod( __FILE__, $hash ) }; ## no critic 'eval' # für Meta.pm (https://forum.fhem.de/index.php/topic,97589.0.html)
@ -399,7 +408,7 @@ sub Set {
my $arg = join " ", map { my $p = $_; $p =~ s/\s//xg; $p; } @a; ## no critic 'Map blocks' my $arg = join " ", map { my $p = $_; $p =~ s/\s//xg; $p; } @a; ## no critic 'Map blocks'
my $prop = shift @a; my $prop = shift @a;
my $prop1 = shift @a; my $prop1 = shift @a;
my ($setlist,@ads); my ($setlist,@ads,$susyid);
my $ad = ""; my $ad = "";
return if(IsDisabled($name)); return if(IsDisabled($name));
@ -417,12 +426,14 @@ sub Set {
"createPortalGraphic:Generation,Consumption,Generation_Consumption,Differential ". "createPortalGraphic:Generation,Consumption,Generation_Consumption,Differential ".
"getData:noArg " "getData:noArg "
; ;
if($hash->{HELPER}{PLANTOID} && $hash->{HELPER}{CONSUMER}) { if($hash->{HELPER}{PLANTOID} && $hash->{HELPER}{CONSUMER}) {
for my $key (keys %{$hash->{HELPER}{CONSUMER}}) { for my $key (keys %{$hash->{HELPER}{CONSUMER}}) {
my $dev = $hash->{HELPER}{CONSUMER}{$key}{DeviceName}; my $dev = $hash->{HELPER}{CONSUMER}{$key}{DeviceName};
if($dev) { if($dev) {
$susyid = $hash->{HELPER}{CONSUMER}{$key}{SUSyID};
push @ads, $dev; push @ads, $dev;
$setlist .= "$dev "; $setlist .= $dev.$hsusyid{$susyid}{arg}." ";
} }
} }
} }
@ -432,18 +443,18 @@ sub Set {
$ad = join "|", @ads; $ad = join "|", @ads;
} }
my ($a,$h) = parseParams ($arg);
my $gcval = $h->{gcval} // 24; # GridConsumptionValue
my $pvval = $h->{pvval} // 76; # PvValue
my $lval = $h->{lval} // 0; # LimitedEnergyValue
$gcval = $gcval/100;
$pvval = $pvval/100;
if ($opt && $ad && $opt =~ /$ad/x) { if ($opt && $ad && $opt =~ /$ad/x) {
# Verbraucher schalten # Verbraucher schalten
#$susyid = 191; # Standard ist Schaltsteckdose
for my $k (keys %{$hash->{HELPER}{CONSUMER}}) {
my $dev = $hash->{HELPER}{CONSUMER}{$k}{DeviceName};
if($opt eq $dev) {
$susyid = $hash->{HELPER}{CONSUMER}{$k}{SUSyID};
last;
}
}
$hash->{HELPER}{GETTER} = "none"; $hash->{HELPER}{GETTER} = "none";
$hash->{HELPER}{SETTER} = "$opt:$gcval#$pvval#$lval"; $hash->{HELPER}{SETTER} = "$opt:$susyid:$prop";
CallInfo($hash); CallInfo($hash);
} }
else { else {
@ -913,7 +924,7 @@ return ($interval,$maxcycles,$timeoutdef);
## schaltet auch Verbraucher des Sunny Home Managers ## schaltet auch Verbraucher des Sunny Home Managers
################################################################ ################################################################
sub GetSetData { ## no critic 'complexity' sub GetSetData { ## no critic 'complexity'
my ($string) = @_; my $string = shift;
my ($name,$getp,$setp) = split("\\|",$string); my ($name,$getp,$setp) = split("\\|",$string);
my $hash = $defs{$name}; my $hash = $defs{$name};
my $cookieLocation = AttrVal($name, "cookieLocation", "./log/".$name."_cookie.txt"); my $cookieLocation = AttrVal($name, "cookieLocation", "./log/".$name."_cookie.txt");
@ -923,6 +934,7 @@ sub GetSetData { ## no critic 'complexity'
my $state = "ok"; my $state = "ok";
my ($st,$lc) = ("",""); my ($st,$lc) = ("","");
my @da = (); my @da = ();
my $params;
my ($errstate,$reread,$retry,$exceed,$newcycle) = (0,0,0,0,0); my ($errstate,$reread,$retry,$exceed,$newcycle) = (0,0,0,0,0);
@ -930,21 +942,11 @@ sub GetSetData { ## no critic 'complexity'
my $randomua = $ak[rand @ak]; my $randomua = $ak[rand @ak];
my $defuseragent = $hua{$randomua}; my $defuseragent = $hua{$randomua};
my $useragent = AttrVal($name, "userAgent", $defuseragent); my $useragent = AttrVal($name, "userAgent", $defuseragent);
BlockingInformParent("FHEM::SMAPortal::setFromBlocking", [$name, "usedUserAgent:$useragent", "NULL" ], 1); BlockingInformParent("FHEM::SMAPortal::setFromBlocking", [$name, "usedUserAgent:$useragent", "NULL" ], 1);
my %hal = ( # Header Accept-Language sprachenabhängig
"DE" => "de,en-US;q=0.7,en;q=0.3",
"EN" => "en-US;q=0.7,en;q=0.3"
);
my ($d,$op);
if($setp ne "none") {
# Verbraucher soll in den Status $op geschaltet werden
($d,$op) = split(":",$setp);
}
Log3 ($name, 5, "$name - Start operation with CookieLocation: $cookieLocation and UserAgent: $useragent"); Log3 ($name, 5, "$name - Start operation with CookieLocation: $cookieLocation and UserAgent: $useragent");
Log3 ($name, 5, "$name - data get: $getp, data set: ".(($d && $op)?($d." ".$op):$setp)); Log3 ($name, 5, "$name - data get: $getp, data set: $setp");
my $ua = LWP::UserAgent->new; my $ua = LWP::UserAgent->new;
@ -999,73 +1001,28 @@ sub GetSetData { ## no critic 'complexity'
} }
} }
### Verbraucher schalten ### Verbraucher schalten / managen
####################################### #######################################
if($setp ne "none") { if($setp ne "none") {
my ($serial,$id,$oid,$h,$oname); my ($d,$susyid,$op) = split(":",$setp); # $op -> Verbraucher Manage Operation
my ($gcval, $pvval, $lval) = split "#",$op; $params = {
name => $name,
ua => $ua,
state => $state,
daref => \@da,
d => $d,
susyid => $susyid,
op => $op
};
for my $key (keys %{$hash->{HELPER}{CONSUMER}}) { if($hsusyid{$susyid} && defined &{$hsusyid{$susyid}{fn}}) {
$h = $hash->{HELPER}{CONSUMER}{$key}{DeviceName}; ($errstate,$state) = &{$hsusyid{$susyid}{fn}} ($params);
if($h && $h eq $d) {
$serial = $hash->{HELPER}{CONSUMER}{$key}{SerialNumber};
$id = $hash->{HELPER}{CONSUMER}{$key}{SUSyID};
$oid = $hash->{HELPER}{CONSUMER}{$key}{ConsumerOid};
$oname = decode("utf8", $hash->{HELPER}{CONSUMER}{$key}{DeviceOrigName});
}
}
my $plantOid = $hash->{HELPER}{PLANTOID};
if($verbose == 5) {
$ua->add_handler( request_send => sub { shift->dump; return } ); # for debugging
$ua->add_handler( response_done => sub { shift->dump; return } );
}
my %fields = ( "Content-Type" => "application/x-www-form-urlencoded",
"Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
);
my $rgb = "rgba(49,101,255,1)";
my $content = [
'UsePriceLimit' => "False",
'UsesCanFrames' => "True",
'ConsumerOid' => "$oid",
'DeviceStatus' => "DeviceActive",
'DataAcceptance' => ["true", "false"],
'PowerConsumerName' => "$oname",
'Priority' => "1",
'RbTimeframeTypeEnergyPv_0' => "pv",
'MaxPriceAllowedValue' => 0,
'GridConsumptionValue' => $gcval,
'PvValue' => $pvval,
'LimitedEnergyValue' => $lval,
'ConsumerIcon' => "/Images/DeviceIcons/ChargingStation.png",
'ConsumerColor.ColorString' => $rgb,
];
my $res = $ua->post("https://www.sunnyportal.com/HoMan/Consumer/Semp/$oid",
%fields,
Content => $content
);
if($verbose == 5) {
Log3 ($name, 5, "$name - Return Code: ".$res->code);
}
$ua->remove_handler('request_send');
$ua->remove_handler('response_done');
$res = $res->decoded_content();
Log3 ($name, 3, "$name - Set \"$d $op\" result: ".$res);
if($res eq "true") {
$state = "ok - switched consumer $d to $op";
BlockingInformParent("FHEM::SMAPortal::setFromBlocking", [$name, "NULL", "GETTER:all" ], 1);
BlockingInformParent("FHEM::SMAPortal::setFromBlocking", [$name, "NULL", "SETTER:none"], 1);
} }
else { else {
$state = "Error - couldn't switch consumer $d to $op"; $errstate = 1;
$state = qq{ERROR - Switch or energy management function for SMA device with SUSyID '$susyid' doesn't exist. Inform Maintainer.};
Log3 ($name, 1, "$name - $state");
} }
} }
@ -1110,21 +1067,21 @@ sub GetSetData { ## no critic 'complexity'
$hash->{HELPER}{RETRIES}++; $hash->{HELPER}{RETRIES}++;
my $cd = AttrVal($name, "cookieDelete", "auto"); my $cd = AttrVal($name, "cookieDelete", "auto");
if($retc == $thold || $cd =~ /Attempt/x) { # Schwellenwert Leseversuche erreicht -> Cookie File löschen if($retc == $thold || $cd =~ /Attempt/x) { # Schwellenwert Leseversuche erreicht -> Cookie File löschen
my $msg = qq{$name - Threshold reached, delete cookie file before retry...}; my $msg = qq{$name - Threshold reached, delete cookie file before retry...};
if($cd =~ /Attempt/x) { if($cd =~ /Attempt/x) {
$msg = qq{$name - force delete cookie file before retry...}; $msg = qq{$name - force delete cookie file before retry...};
} }
Log3 ($name, 3, $msg); Log3 ($name, 3, $msg);
sleep $sleepretry; # Threshold exceed -> Retry mit Cookie löschen sleep $sleepretry; # Threshold exceed -> Retry mit Cookie löschen
$exceed = 1; $exceed = 1;
BlockingInformParent("FHEM::SMAPortal::setFromBlocking", [$name, "NULL", "RETRIES:".$hash->{HELPER}{RETRIES} ], 1); BlockingInformParent("FHEM::SMAPortal::setFromBlocking", [$name, "NULL", "RETRIES:".$hash->{HELPER}{RETRIES} ], 1);
return "$name|$exceed|$newcycle|$errstate|$getp|$setp"; return "$name|$exceed|$newcycle|$errstate|$getp|$setp";
} }
sleep $sleepretry; sleep $sleepretry;
goto &GetSetData; goto &GetSetData;
} }
@ -1147,7 +1104,7 @@ sub GetSetData { ## no critic 'complexity'
} }
# Daten müssen als Einzeiler zurückgegeben werden # Daten müssen als Einzeiler zurückgegeben werden
$st = encode_base64 ($state, ""); $st = encode_base64 ($state, "");
if(@da) { if(@da) {
$lc = join "###", @da; $lc = join "###", @da;
$lc = encode_base64 ($lc, ""); $lc = encode_base64 ($lc, "");
@ -1171,7 +1128,7 @@ sub _doLogin {
my $v5d = AttrVal($name, "verbose5Data", "none"); my $v5d = AttrVal($name, "verbose5Data", "none");
my $verbose = AttrVal($name, "verbose", 3); my $verbose = AttrVal($name, "verbose", 3);
if($verbose == 5 && $v5d =~ /loginData/) { if($verbose == 5 && $v5d =~ /loginData/x) {
$ua->add_handler( request_send => sub { shift->dump; return } ); # for debugging $ua->add_handler( request_send => sub { shift->dump; return } ); # for debugging
$ua->add_handler( response_done => sub { shift->dump; return } ); $ua->add_handler( response_done => sub { shift->dump; return } );
} }
@ -1184,7 +1141,7 @@ sub _doLogin {
my $cookie = $loginp->header('Set-Cookie') // ""; my $cookie = $loginp->header('Set-Cookie') // "";
if ($loginp->is_success) { if ($loginp->is_success) {
if($v5d =~ /loginData/) { if($v5d =~ /loginData/x) {
Log3 ($name, 5, "$name - Status Login Page: ".$loginp->status_line); Log3 ($name, 5, "$name - Status Login Page: ".$loginp->status_line);
Log3 ($name, 5, "$name - Header Location: ". $location); Log3 ($name, 5, "$name - Header Location: ". $location);
Log3 ($name, 5, "$name - Header Set-Cookie: ".$cookie); Log3 ($name, 5, "$name - Header Set-Cookie: ".$cookie);
@ -1280,6 +1237,132 @@ sub __isLoggedIn {
return 0; return 0;
} }
################################################################
# Consumer schalten
################################################################
sub _switchConsumer {
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 $d = $paref->{d};
my $susyid = $paref->{susyid};
my $op = $paref->{op};
my $hash = $defs{$name};
my ($serial,$id);
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};
}
}
my $plantOid = $hash->{HELPER}{PLANTOID};
my $errstate = 0;
my %fields = ("Content-Type" => "application/x-www-form-urlencoded; charset=UTF-8");
my $tag = "switchConsumer";
my $cont = {
'mode' => $op,
'serialNumber' => $serial,
'SUSyID' => $susyid,
'plantOid' => $plantOid
};
($errstate,$state) = __dispatchPost ({ name => $name,
ua => $ua,
call => 'https://www.sunnyportal.com/Homan/ConsumerBalance/SetOperatingMode',
tag => $tag,
state => $state,
fnaref => [ qw( extractSwitchConsumerData ) ],
fields => \%fields,
content => $cont,
addon => "$d:$susyid:$op", # optionales Addon für aufzurufende Funktion
daref => $daref
});
return ($errstate,$state);
}
################################################################
# Consumer Management abhängig von erzeugter PV-Energie
# (z.B. EV Charger)
# $op: enthält den Anteil der erzeugten PV der vorhanden
# muß bevor der Verbraucher eingeschaltet werden sollen
# (der Anteil bezogener Energie ergibt sich aus
# 100% - PV)
################################################################
sub _manageConsumerByEnergy {
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 $d = $paref->{d};
my $susyid = $paref->{susyid};
my $op = $paref->{op};
my $hash = $defs{$name};
my $pvval = $op/100;
my $gcval = 1-$pvval;
my ($serial,$oid,$oname);
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};
$oid = $hash->{HELPER}{CONSUMER}{$key}{ConsumerOid};
$oname = decode("utf8", $hash->{HELPER}{CONSUMER}{$key}{DeviceOrigName});
}
}
my $gclog = 100-$op;
Log3 ($name, 4, qq{$name - Manage consumer "$d" (SuSyID $susyid): switch on if condition PV=$op% (GridConsumption=$gclog%) is fulfilled });
my $plantOid = $hash->{HELPER}{PLANTOID};
my $errstate = 0;
my %fields = ( "Content-Type" => "application/x-www-form-urlencoded",
"Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
);
my $rgb = "rgba(49,101,255,1)";
my $cont = [
'UsePriceLimit' => "False",
'UsesCanFrames' => "True",
'ConsumerOid' => "$oid",
'DeviceStatus' => "DeviceActive",
'DataAcceptance' => ["true", "false"],
'PowerConsumerName' => "$oname",
'Priority' => "1",
'RbTimeframeTypeEnergyPv_0' => "pv",
'MaxPriceAllowedValue' => 0,
'GridConsumptionValue' => $gcval,
'PvValue' => $pvval,
'LimitedEnergyValue' => 0,
'ConsumerIcon' => "/Images/DeviceIcons/ChargingStation.png",
'ConsumerColor.ColorString' => $rgb,
];
my $tag = "switchConsumer";
($errstate,$state) = __dispatchPost ({ name => $name,
ua => $ua,
call => 'https://www.sunnyportal.com/HoMan/Consumer/Semp/$oid',
tag => $tag,
state => $state,
fnaref => [ qw( extractSwitchConsumerData ) ],
fields => \%fields,
content => $cont,
addon => "$d:$susyid:$op", # optionales Addon für aufzurufende Funktion
daref => $daref
});
return ($errstate,$state);
}
################################################################ ################################################################
# Abruf Live Daten # Abruf Live Daten
################################################################ ################################################################
@ -1431,7 +1514,7 @@ sub _getConsumerDayData { ## no critic "not used"
if(!$hash->{HELPER}{PLANTOID}) { if(!$hash->{HELPER}{PLANTOID}) {
$errstate = 1; $errstate = 1;
$state = qq{The consumer data cannot be retrieved because the plant ID isn't set.}; $state = qq{The consumer data cannot be retrieved because the plant ID isn't set.};
Log3 $name, 2, "$name - $state"; Log3 ($name, 2, "$name - $state");
return ($errstate,$state,$reread,$retry); return ($errstate,$state,$reread,$retry);
} }
@ -1475,7 +1558,7 @@ sub _getConsumerMonthData { ## no critic "not used"
if(!$hash->{HELPER}{PLANTOID}) { if(!$hash->{HELPER}{PLANTOID}) {
$errstate = 1; $errstate = 1;
$state = qq{The consumer data cannot be retrieved because the plant ID isn't set.}; $state = qq{The consumer data cannot be retrieved because the plant ID isn't set.};
Log3 $name, 2, "$name - $state"; Log3 ($name, 2, "$name - $state");
return ($errstate,$state,$reread,$retry); return ($errstate,$state,$reread,$retry);
} }
@ -1528,7 +1611,7 @@ sub _getConsumerYearData { ## no critic "not used"
if(!$hash->{HELPER}{PLANTOID}) { if(!$hash->{HELPER}{PLANTOID}) {
$errstate = 1; $errstate = 1;
$state = qq{The consumer data cannot be retrieved because of the plant ID isn't set.}; $state = qq{The consumer data cannot be retrieved because of the plant ID isn't set.};
Log3 $name, 2, "$name - $state"; Log3 ($name, 2, "$name - $state");
return ($errstate,$state,$reread,$retry); return ($errstate,$state,$reread,$retry);
} }
@ -2049,7 +2132,7 @@ sub __dispatchPost {
my @func = @$fnref; my @func = @$fnref;
no strict "refs"; ## no critic 'NoStrict' no strict "refs"; ## no critic 'NoStrict'
for my $fn (@func) { for my $fn (@func) {
&{$fn} ($hash,$daref,$data_cont,$fnaddon,$tag); $state = &{$fn} ($hash,$daref,$data_cont,$fnaddon,$tag) // $state;
} }
use strict "refs"; use strict "refs";
} }
@ -2149,6 +2232,7 @@ sub ___analyzeData { ## no critic 'complexity'
my $hash = $defs{$name}; my $hash = $defs{$name};
my ($reread,$retry) = (0,0); my ($reread,$retry) = (0,0);
my $data = ""; my $data = "";
my $decerror = 0; # JSON Dekodierfehler
my $v5d = AttrVal($name, "verbose5Data", "none"); my $v5d = AttrVal($name, "verbose5Data", "none");
my $ad_content = encode("utf8", $ad->decoded_content); my $ad_content = encode("utf8", $ad->decoded_content);
@ -2169,7 +2253,9 @@ sub ___analyzeData { ## no critic 'complexity'
name => $name, name => $name,
}); });
$data = eval{decode_json($ad_content)} or do { $data = $ad_content }; $data = eval{decode_json($ad_content)} or do { $data = $ad_content;
$decerror = 1;
};
my $jsonerror = $ad->header('Jsonerror') // ""; # Portal meldet keine Verarbeitung des Reaquests möglich (z.B. Jahr 0000 zur Auswertung angefordert) my $jsonerror = $ad->header('Jsonerror') // ""; # Portal meldet keine Verarbeitung des Reaquests möglich (z.B. Jahr 0000 zur Auswertung angefordert)
@ -2179,7 +2265,7 @@ sub ___analyzeData { ## no critic 'complexity'
return ($reread,$retry,$errstate,$state); return ($reread,$retry,$errstate,$state);
} }
if(ref $data eq "HASH") { if(!$decerror && ref $data eq "HASH") { # es wurde JSON empfangen und Ergebnis ist ein HASH
for my $k (keys %{$data}) { for my $k (keys %{$data}) {
my $val = $data->{$k}; my $val = $data->{$k};
next if(!defined $val); next if(!defined $val);
@ -2226,18 +2312,22 @@ sub ___analyzeData { ## no critic 'complexity'
} }
} }
} }
elsif (!$decerror) { # es wurde JSON empfangen aber Ergebnis ist KEIN HASH
Log3 ($name, 5, "$name - decoded Content received: ". jboolmap($data));
}
else { else {
my $njdat = encode("utf8", $ad->as_string); my $njdat = encode("utf8", $ad->as_string);
if($njdat =~ /401\s-\sUnauthorized/x) { if($njdat =~ /401\s-\sUnauthorized/x) {
Log3 ($name, 2, "$name - ERROR - User logged in but unauthorized"); Log3 ($name, 2, "$name - ERROR - User logged in but unauthorized");
my($p1,$p2) = $njdat =~ /<h2>401\s-\sUnauthorized:.(.*)?<\/h2>.*?<h3>(.*)?<\/h3>/sx; my($p1,$p2) = $njdat =~ /<h2>401\s-\sUnauthorized:.(.*)?<\/h2>.*?<h3>(.*)?<\/h3>/sx;
$state = ($p1 // "")." ".($p2 // ""); $state = ($p1 // "")." ".($p2 // "");
} }
$njdat = encode("utf8", $ad->decoded_content);
Log3 ($name, 5, "$name - No JSON Data received:\n ".$njdat); Log3 ($name, 5, "$name - No JSON Data received:\n ".$njdat);
$errstate = 1; $errstate = 1;
$state = "ERROR - see logfile for further information";
} }
return ($reread,$retry,$errstate,$state); return ($reread,$retry,$errstate,$state);
@ -2341,8 +2431,8 @@ sub ParseData {
if(!$errstate) { if(!$errstate) {
if($setp ne "none") { if($setp ne "none") {
my ($d,$op) = split(":",$setp); my ($d,$susyid,$op) = split(":",$setp);
$op = ($op eq "auto") ? "off (automatic)" : $op; $op = ($op eq "auto") ? "off (automatic)" : $op;
readingsBulkUpdate($hash, "${cclv}_${d}_Switch", $op); readingsBulkUpdate($hash, "${cclv}_${d}_Switch", $op);
} }
readingsBulkUpdate($hash, "lastCycleTime", $ctime ) if($ctime > 0); readingsBulkUpdate($hash, "lastCycleTime", $ctime ) if($ctime > 0);
@ -2930,10 +3020,10 @@ sub extractConsumerMasterdata {
$consumers{"${i}_ConsumerLfd"} = $i; $consumers{"${i}_ConsumerLfd"} = $i;
my $cn = $consumers{"${i}_ConsumerName"}; # Verbrauchername my $cn = $consumers{"${i}_ConsumerName"}; # Verbrauchername
next if(!$cn); next if(!$cn);
$cn = replaceJunkSigns($cn); # Verbrauchername gemäß Readingreguarien verändern $cn = replaceJunkSigns($cn); # Verbrauchername gemäß Readingreguarien anpassen
$hcon{$i}{DeviceName} = $cn; $hcon{$i}{DeviceName} = $cn;
$hcon{$i}{DeviceOrigName} = encode("utf8", $c->{'DeviceName'}); # der originale Verbrauchername $hcon{$i}{DeviceOrigName} = encode("utf8", $c->{'DeviceName'}); # der originale Verbrauchername
$hcon{$i}{ConsumerOid} = $consumers{"${i}_ConsumerOid"}; $hcon{$i}{ConsumerOid} = $consumers{"${i}_ConsumerOid"};
$hcon{$i}{SerialNumber} = $c->{'SerialNumber'}; $hcon{$i}{SerialNumber} = $c->{'SerialNumber'};
$hcon{$i}{SUSyID} = $c->{'SUSyID'}; $hcon{$i}{SUSyID} = $c->{'SUSyID'};
@ -3096,6 +3186,36 @@ sub extractConsumerHistData { #
return; return;
} }
################################################################
# Auswertung Ergebnis aus Switch Consumer
################################################################
sub extractSwitchConsumerData {
my $hash = shift;
my $daref = shift; # Referenz zum Datenarray
my $jdata = shift; # empfangene JSON-Daten
my $addon = shift; # ein optionales AddOn
my $tag = shift; # Kennzeichen der abgerufenen Daten/ der Abrufroutine
my $name = $hash->{NAME};
Log3 ($name, 4, "$name - extracting Switch Consumer result ");
my ($d,$susyid,$op) = split(":",$addon); # $op -> Verbraucher Manage Operation
Log3 ($name, 3, qq{$name - Set "$d $op" result: $jdata});
my $state;
if($jdata eq "true") {
$state = "ok - switched consumer $d to $op";
BlockingInformParent("FHEM::SMAPortal::setFromBlocking", [$name, "NULL", "GETTER:all" ], 1);
BlockingInformParent("FHEM::SMAPortal::setFromBlocking", [$name, "NULL", "SETTER:none"], 1);
}
else {
$state = "ERROR - couldn't switch consumer $d to $op";
}
return $state;
}
################################################################ ################################################################
# Auswertung Daten aus Hilfsroutinen # Auswertung Daten aus Hilfsroutinen
################################################################ ################################################################
@ -3417,6 +3537,28 @@ sub replaceJunkSigns {
return($rn); return($rn);
} }
###############################################################################
# JSON Boolean Test und Mapping
# $var = Variante der boolean Auswertung:
# "char": Rückgabe von true / false für wahr / falsch
# "bin" : Rückgabe von 1 / 0 für wahr / falsch
###############################################################################
sub jboolmap {
my $bool = shift;
my $var = shift // "char";
my $true = ($var eq "char") ? "true" : 1;
my $false = ($var eq "char") ? "false" : 0;
my $is_boolean = JSON::is_bool($bool);
if($is_boolean) {
$bool = $bool ? $true : $false;
}
return $bool;
}
############################################################################### ###############################################################################
# Subroutine für Portalgrafik # Subroutine für Portalgrafik
############################################################################### ###############################################################################
@ -4984,7 +5126,8 @@ return;
"LWP": 0, "LWP": 0,
"HTTP::Cookies": 0, "HTTP::Cookies": 0,
"MIME::Base64": 0, "MIME::Base64": 0,
"utf8": 0 "utf8": 0,
"Color": 0
}, },
"recommends": { "recommends": {
"FHEM::Meta": 0 "FHEM::Meta": 0