2019-03-03 20:08:04 +00:00
#########################################################################################################################
2019-03-16 06:48:59 +00:00
# $Id: 76_SMAPortal.pm 00000 2019-03-14 20:21:11Z DS_Starter $
2019-03-03 20:08:04 +00:00
#########################################################################################################################
# 76_SMAPortal.pm
#
# (c) 2019 by Heiko Maaz
# e-mail: Heiko dot Maaz at t-online dot de
#
2019-03-10 07:34:29 +00:00
# This module can be used to get data from SMA Portal https://www.sunnyportal.com/Templates/Start.aspx .
2019-03-03 20:08:04 +00:00
#
# This script is part of fhem.
#
# Fhem is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# Fhem is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with fhem. If not, see <http://www.gnu.org/licenses/>.
#
# This module is based on the modules (Thanks to all!):
# 98_SHM.pm from author Brun von der Gönne <brun at goenne dot de>
# 98_SHMForecastRelative.pm from author BerndArnold
#
# FHEM Forum: http://forum.fhem.de/index.php/topic,27667.0.html
#
#########################################################################################################################
#
# Definition: define <name> SMAPortal
#
#########################################################################################################################
package main ;
use strict ;
use warnings ;
2019-03-16 06:48:59 +00:00
use FHEM::Meta ;
2019-03-03 20:08:04 +00:00
###############################################################
# SMAPortal Initialize
2019-03-16 06:48:59 +00:00
# Da ich mit package arbeite müssen für die jeweiligen hashFn
# Funktionen der Funktionsname und davor mit :: getrennt der
# eigentliche package Name des Modules
2019-03-03 20:08:04 +00:00
###############################################################
sub SMAPortal_Initialize ($) {
my ( $ hash ) = @ _ ;
2019-03-16 06:48:59 +00:00
$ hash - > { DefFn } = "FHEM::SMAPortal::Define" ;
$ hash - > { UndefFn } = "FHEM::SMAPortal::Undefine" ;
$ hash - > { DeleteFn } = "FHEM::SMAPortal::Delete" ;
$ hash - > { AttrFn } = "FHEM::SMAPortal::Attr" ;
$ hash - > { SetFn } = "FHEM::SMAPortal::Set" ;
$ hash - > { GetFn } = "FHEM::SMAPortal::Get" ;
2019-03-09 08:32:50 +00:00
$ hash - > { AttrList } = "cookieLocation " .
"cookielifetime " .
2019-03-03 20:08:04 +00:00
"detailLevel:1,2,3,4 " .
2019-03-09 08:32:50 +00:00
"disable:0,1 " .
"getDataRetries:1,2,3,4,5,6,7,8,9,10 " .
"interval " .
"showPassInLog:1,0 " .
2019-03-03 20:08:04 +00:00
"timeout " .
"userAgent " .
$ readingFnAttributes ;
2019-03-11 22:54:31 +00:00
2019-03-16 06:48:59 +00:00
FHEM::Meta:: InitMod ( __FILE__ , $ hash ) ; # für Meta.pm (https://forum.fhem.de/index.php/topic,97589.0.html)
return ;
2019-03-11 22:54:31 +00:00
}
2019-03-03 20:08:04 +00:00
2019-03-16 06:48:59 +00:00
###############################################################
# Begin Package
###############################################################
package FHEM::SMAPortal ;
use strict ;
use warnings ;
use GPUtils qw( :all ) ; # wird für den Import der FHEM Funktionen aus der fhem.pl benötigt
use POSIX ;
use FHEM::Meta ;
use Data::Dumper ;
use Blocking ;
use Time::HiRes qw( gettimeofday ) ;
use LWP::UserAgent ;
use HTTP::Cookies ;
use JSON qw( decode_json ) ;
use MIME::Base64 ;
# Run before module compilation
BEGIN {
# Import from main::
GP_Import (
qw(
attr
AttrVal
addToDevAttrList
addToAttrList
BlockingCall
BlockingKill
CommandAttr
CommandDeleteAttr
CommandDeleteReading
CommandSet
defs
delFromDevAttrList
delFromAttrList
devspec2array
deviceEvents
Debug
FmtDateTime
FmtTime
fhemTimeGm
getKeyValue
gettimeofday
genUUID
init_done
InternalTimer
IsDisabled
Log
Log3
modules
readingsSingleUpdate
readingsBulkUpdate
readingsBulkUpdateIfChanged
readingsBeginUpdate
readingsEndUpdate
ReadingsVal
RemoveInternalTimer
setKeyValue
2019-03-19 18:11:34 +00:00
sortTopicNum
2019-03-16 06:48:59 +00:00
TimeNow
Value
)
) ;
}
# Versions History intern
our % vNotesIntern = (
2019-03-18 23:51:42 +00:00
"1.3.0" = > "18.03.2019 change module to use package FHEM::SMAPortal and use Meta.pm, new sub setVersionInfo" ,
2019-03-16 06:48:59 +00:00
"1.2.3" = > "12.03.2019 make ready for 98_Installer.pm " ,
"1.2.2" = > "11.03.2019 new Errormessage analyze added, make ready for Meta.pm " ,
"1.2.1" = > "10.03.2019 behavior of state changed, commandref revised " ,
"1.2.0" = > "09.03.2019 integrate weather data, minor fixes " ,
"1.1.0" = > "09.03.2019 make get data more stable, new attribute \"getDataRetries\" " ,
"1.0.0" = > "03.03.2019 initial "
) ;
2019-03-03 20:08:04 +00:00
###############################################################
# SMAPortal Define
###############################################################
2019-03-16 06:48:59 +00:00
sub Define ($$) {
2019-03-03 20:08:04 +00:00
my ( $ hash , $ def ) = @ _ ;
my @ a = split ( /\s+/ , $ def ) ;
return "Wrong syntax: use \"define <name> SMAPortal\" " if ( int ( @ a ) < 1 ) ;
2019-03-16 06:48:59 +00:00
# Versionsinformationen setzen
setVersionInfo ( $ hash ) ;
2019-03-03 20:08:04 +00:00
2019-03-16 06:48:59 +00:00
getcredentials ( $ hash , 1 ) ; # Credentials lesen und in RAM laden ($boot=1)
CallInfo ( $ hash ) ; # Start Daten Abrufschleife
delcookiefile ( $ hash ) ; # Start Schleife regelmäßiges Löschen Cookiefile
2019-03-03 20:08:04 +00:00
return undef ;
}
###############################################################
# SMAPortal Undefine
###############################################################
2019-03-16 06:48:59 +00:00
sub Undefine ($$) {
2019-03-03 20:08:04 +00:00
my ( $ hash , $ arg ) = @ _ ;
RemoveInternalTimer ( $ hash ) ;
BlockingKill ( $ hash - > { HELPER } { RUNNING_PID } ) if ( $ hash - > { HELPER } { RUNNING_PID } ) ;
return undef ;
}
###############################################################
# SMAPortal Delete
###############################################################
2019-03-16 06:48:59 +00:00
sub Delete ($$) {
2019-03-03 20:08:04 +00:00
my ( $ hash , $ arg ) = @ _ ;
my $ index = $ hash - > { TYPE } . "_" . $ hash - > { NAME } . "_credentials" ;
my $ name = $ hash - > { NAME } ;
# gespeicherte Credentials löschen
setKeyValue ( $ index , undef ) ;
return undef ;
}
###############################################################
# SMAPortal Set
###############################################################
2019-03-16 06:48:59 +00:00
sub Set ($@) {
2019-03-03 20:08:04 +00:00
my ( $ hash , @ a ) = @ _ ;
return "\"set X\" needs at least an argument" if ( @ a < 2 ) ;
my $ name = $ a [ 0 ] ;
my $ opt = $ a [ 1 ] ;
my $ prop = $ a [ 2 ] ;
my $ prop1 = $ a [ 3 ] ;
my ( $ setlist , $ success ) ;
return if ( IsDisabled ( $ name ) ) ;
if ( ! $ hash - > { CREDENTIALS } ) {
# initiale setlist für neue Devices
$ setlist = "Unknown argument $opt, choose one of " .
"credentials "
;
} else {
# erweiterte Setlist wenn Credentials gesetzt
$ setlist = "Unknown argument $opt, choose one of " .
"credentials "
;
}
if ( $ opt eq "credentials" ) {
return "Credentials are incomplete, use username password" if ( ! $ prop || ! $ prop1 ) ;
2019-03-16 06:48:59 +00:00
( $ success ) = setcredentials ( $ hash , $ prop , $ prop1 ) ;
2019-03-03 20:08:04 +00:00
if ( $ success ) {
2019-03-16 06:48:59 +00:00
CallInfo ( $ hash ) ;
2019-03-03 20:08:04 +00:00
return "Username and Password saved successfully" ;
} else {
return "Error while saving Username / Password - see logfile for details" ;
}
} else {
return "$setlist" ;
}
return ;
}
######################################################################################
# Username / Paßwort speichern
######################################################################################
2019-03-16 06:48:59 +00:00
sub setcredentials ($@) {
2019-03-03 20:08:04 +00:00
my ( $ hash , @ credentials ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my ( $ success , $ credstr , $ index , $ retcode ) ;
my ( @ key , $ len , $ i ) ;
$ credstr = encode_base64 ( join ( ':' , @ credentials ) ) ;
# Beginn Scramble-Routine
@ key = qw( 1 3 4 5 6 3 2 1 9 ) ;
$ len = scalar @ key ;
$ i = 0 ;
$ credstr = join "" ,
map { $ i = ( $ i + 1 ) % $ len ;
chr ( ( ord ( $ _ ) + $ key [ $ i ] ) % 256 ) } split // , $ credstr ;
# End Scramble-Routine
$ index = $ hash - > { TYPE } . "_" . $ hash - > { NAME } . "_credentials" ;
$ retcode = setKeyValue ( $ index , $ credstr ) ;
if ( $ retcode ) {
Log3 ( $ name , 1 , "$name - Error while saving the Credentials - $retcode" ) ;
$ success = 0 ;
} else {
2019-03-16 06:48:59 +00:00
getcredentials ( $ hash , 1 ) ; # Credentials nach Speicherung lesen und in RAM laden ($boot=1)
2019-03-03 20:08:04 +00:00
$ success = 1 ;
}
return ( $ success ) ;
}
######################################################################################
# Username / Paßwort abrufen
######################################################################################
2019-03-16 06:48:59 +00:00
sub getcredentials ($$) {
2019-03-03 20:08:04 +00:00
my ( $ hash , $ boot ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my ( $ success , $ username , $ passwd , $ index , $ retcode , $ credstr ) ;
my ( @ key , $ len , $ i ) ;
if ( $ boot ) {
# mit $boot=1 Credentials von Platte lesen und als scrambled-String in RAM legen
$ index = $ hash - > { TYPE } . "_" . $ hash - > { NAME } . "_credentials" ;
( $ retcode , $ credstr ) = getKeyValue ( $ index ) ;
if ( $ retcode ) {
Log3 ( $ name , 2 , "$name - Unable to read password from file: $retcode" ) ;
$ success = 0 ;
}
if ( $ credstr ) {
# beim Boot scrambled Credentials in den RAM laden
$ hash - > { HELPER } { CREDENTIALS } = $ credstr ;
# "Credentials" wird als Statusbit ausgewertet. Wenn nicht gesetzt -> Warnmeldung und keine weitere Verarbeitung
$ hash - > { CREDENTIALS } = "Set" ;
$ success = 1 ;
}
} else {
# boot = 0 -> Credentials aus RAM lesen, decoden und zurückgeben
$ credstr = $ hash - > { HELPER } { CREDENTIALS } ;
if ( $ credstr ) {
# Beginn Descramble-Routine
@ key = qw( 1 3 4 5 6 3 2 1 9 ) ;
$ len = scalar @ key ;
$ i = 0 ;
$ credstr = join "" ,
map { $ i = ( $ i + 1 ) % $ len ;
chr ( ( ord ( $ _ ) - $ key [ $ i ] + 256 ) % 256 ) }
split // , $ credstr ;
# Ende Descramble-Routine
( $ username , $ passwd ) = split ( ":" , decode_base64 ( $ credstr ) ) ;
my $ logpw = AttrVal ( $ name , "showPassInLog" , "0" ) == 1 ? $ passwd : "********" ;
Log3 ( $ name , 3 , "$name - Credentials read from RAM: $username $logpw" ) ;
} else {
Log3 ( $ name , 1 , "$name - Credentials not set in RAM !" ) ;
}
$ success = ( defined ( $ passwd ) ) ? 1 : 0 ;
}
return ( $ success , $ username , $ passwd ) ;
}
###############################################################
# SMAPortal Get
###############################################################
2019-03-16 06:48:59 +00:00
sub Get ($$) {
2019-03-03 20:08:04 +00:00
my ( $ hash , @ a ) = @ _ ;
return "\"get X\" needs at least an argument" if ( @ a < 2 ) ;
my $ name = shift @ a ;
my $ opt = shift @ a ;
my $ getlist = "Unknown argument $opt, choose one of " .
"storedCredentials:noArg " .
"data:noArg " ;
return "module is disabled" if ( IsDisabled ( $ name ) ) ;
if ( $ opt eq "data" ) {
2019-03-16 06:48:59 +00:00
CallInfo ( $ hash ) ;
2019-03-03 20:08:04 +00:00
} elsif ( $ opt eq "storedCredentials" ) {
if ( ! $ hash - > { CREDENTIALS } ) { return "Credentials of $name are not set - make sure you've set it with \"set $name credentials <username> <password>\"" ; }
# Credentials abrufen
2019-03-16 06:48:59 +00:00
my ( $ success , $ username , $ password ) = getcredentials ( $ hash , 0 ) ;
2019-03-03 20:08:04 +00:00
unless ( $ success ) { return "Credentials couldn't be retrieved successfully - see logfile" } ;
return "Stored Credentials to access SMA Portal:\n" .
"========================================\n" .
"Username: $username, Password: $password\n" .
"\n" ;
} else {
return "$getlist" ;
}
return undef ;
}
###############################################################
# SMAPortal Attr
###############################################################
2019-03-16 06:48:59 +00:00
sub Attr ($$$$) {
2019-03-03 20:08:04 +00:00
my ( $ cmd , $ name , $ aName , $ aVal ) = @ _ ;
my $ hash = $ defs { $ name } ;
my ( $ do , $ val ) ;
# $cmd can be "del" or "set"
# $name is device name
# aName and aVal are Attribute name and value
if ( $ aName eq "disable" ) {
if ( $ cmd eq "set" ) {
$ do = ( $ aVal ) ? 1 : 0 ;
}
$ do = 0 if ( $ cmd eq "del" ) ;
$ val = ( $ do == 1 ? "disabled" : "initialized" ) ;
if ( $ do ) {
2019-03-16 06:48:59 +00:00
delread ( $ hash ) ;
2019-03-03 20:08:04 +00:00
delete $ hash - > { MODE } ;
RemoveInternalTimer ( $ hash ) ;
2019-03-16 06:48:59 +00:00
delcookiefile ( $ hash , 1 ) ;
2019-03-03 20:08:04 +00:00
} else {
2019-03-16 06:48:59 +00:00
InternalTimer ( gettimeofday ( ) + 1.0 , "FHEM::SMAPortal::CallInfo" , $ hash , 0 ) ;
InternalTimer ( gettimeofday ( ) + 5.0 , "FHEM::SMAPortal::delcookiefile" , $ hash , 0 ) ;
2019-03-03 20:08:04 +00:00
}
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdate ( $ hash , "state" , $ val ) ;
readingsEndUpdate ( $ hash , 1 ) ;
}
if ( $ cmd eq "set" ) {
if ( $ aName =~ m/timeout|interval/ ) {
unless ( $ aVal =~ /^\d+$/ ) { return " The Value for $aName is not valid. Use only figures 0-9 !" ; }
}
if ( $ aName =~ m/interval/ ) {
2019-03-16 06:48:59 +00:00
InternalTimer ( gettimeofday ( ) + 1.0 , "FHEM::SMAPortal::CallInfo" , $ hash , 0 ) ;
2019-03-03 20:08:04 +00:00
}
}
return undef ;
}
################################################################
## Hauptschleife BlockingCall
################################################################
2019-03-16 06:48:59 +00:00
sub CallInfo ($) {
2019-03-03 20:08:04 +00:00
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ timeout = AttrVal ( $ name , "timeout" , 30 ) ;
my $ interval = AttrVal ( $ name , "interval" , 300 ) ;
my $ new ;
2019-03-16 06:48:59 +00:00
RemoveInternalTimer ( $ hash , "FHEM::SMAPortal::CallInfo" ) ;
2019-03-03 20:08:04 +00:00
if ( $ init_done == 1 ) {
if ( ! $ hash - > { CREDENTIALS } ) {
Log3 ( $ name , 1 , "$name - Credentials not set. Set it with \"set $name credentials <username> <password>\"" ) ;
2019-03-09 08:32:50 +00:00
readingsSingleUpdate ( $ hash , "state" , "Credentials not set" , 1 ) ;
2019-03-03 20:08:04 +00:00
return ;
}
if ( ! $ interval ) {
$ hash - > { MODE } = "Manual" ;
} else {
$ new = gettimeofday ( ) + $ interval ;
2019-03-16 06:48:59 +00:00
InternalTimer ( $ new , "FHEM::SMAPortal::CallInfo" , $ hash , 0 ) ;
2019-03-03 20:08:04 +00:00
$ hash - > { MODE } = "Automatic - next polltime: " . FmtTime ( $ new ) ;
}
return if ( IsDisabled ( $ name ) ) ;
if ( $ hash - > { HELPER } { RUNNING_PID } ) {
BlockingKill ( $ hash - > { HELPER } { RUNNING_PID } ) ;
delete ( $ hash - > { HELPER } { RUNNING_PID } ) ;
}
2019-03-09 08:32:50 +00:00
$ hash - > { HELPER } { RETRIES } = AttrVal ( $ name , "getDataRetries" , 3 ) ;
2019-03-16 06:48:59 +00:00
$ hash - > { HELPER } { RUNNING_PID } = BlockingCall ( "FHEM::SMAPortal::GetData" , $ name , "FHEM::SMAPortal::ParseData" , $ timeout , "FHEM::SMAPortal::ParseAborted" , $ hash ) ;
2019-03-03 20:08:04 +00:00
$ hash - > { HELPER } { RUNNING_PID } { loglevel } = 5 if ( $ hash - > { HELPER } { RUNNING_PID } ) ; # Forum #77057
} else {
2019-03-16 06:48:59 +00:00
InternalTimer ( gettimeofday ( ) + 5 , "FHEM::SMAPortal::CallInfo" , $ hash , 0 ) ;
2019-03-03 20:08:04 +00:00
}
return ;
}
################################################################
## Datenabruf SMA-Portal
################################################################
2019-03-16 06:48:59 +00:00
sub GetData ($) {
2019-03-03 20:08:04 +00:00
my ( $ name ) = @ _ ;
my $ hash = $ defs { $ name } ;
my ( $ livedata_content ) ;
my $ login_state = 0 ;
2019-03-09 08:32:50 +00:00
my ( $ forecast_content , $ weatherdata_content ) = ( "" , "" ) ;
2019-03-03 20:08:04 +00:00
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/mycookies.txt" ) ;
2019-03-16 06:48:59 +00:00
Log3 $ name , 5 , "$name - Start BlockingCall GetData with CookieLocation: $cookieLocation and UserAgent: $useragent" ;
2019-03-03 20:08:04 +00:00
my $ ua = LWP::UserAgent - > new ;
# Define user agent type
$ ua - > agent ( "$useragent" ) ;
# Cookies
$ ua - > cookie_jar ( HTTP::Cookies - > new ( file = > "$cookieLocation" ,
ignore_discard = > 1 ,
autosave = > 1
)
) ;
# Sunny Home Manager Seite abfragen
my $ livedata = $ ua - > get ( 'https://www.sunnyportal.com/homemanager' ) ;
if ( ( $ livedata - > content =~ m/FeedIn/i ) && ( $ livedata - > content !~ m/expired/i ) ) {
Log3 $ name , 4 , "$name - Login to SMA-Portal succesful" ;
# JSON Live Daten
$ livedata_content = $ livedata - > content ;
$ login_state = 1 ;
Log3 $ name , 4 , "$name - Getting live data now" ;
Log3 $ name , 5 , "$name - Data received:\n" . Dumper decode_json ( $ livedata_content ) ;
2019-03-09 08:32:50 +00:00
# JSON Wetterdaten
Log3 $ name , 4 , "$name - Getting weather data now" ;
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 ) ;
2019-03-03 20:08:04 +00:00
# JSON Forecast Daten
my $ dl = AttrVal ( $ name , "detailLevel" , 1 ) ;
if ( $ dl > 1 ) {
Log3 $ name , 5 , "$name - Getting forecast data now" ;
my $ forecast_page = $ ua - > get ( 'https://www.sunnyportal.com/HoMan/Forecast/LoadRecommendationData' ) ;
Log3 $ name , 5 , "$name - Return Code: " . $ forecast_page - > code ;
if ( $ forecast_page - > content =~ m/ForecastChartDataPoint/i ) {
$ forecast_content = $ forecast_page - > content ;
Log3 $ name , 5 , "$name - Forecast Data received:\n" . $ forecast_content ;
}
}
} else {
my $ usernameField = "ctl00\$ContentPlaceHolder1\$Logincontrol1\$txtUserName" ;
my $ passwordField = "ctl00\$ContentPlaceHolder1\$Logincontrol1\$txtPassword" ;
my $ loginField = "__EVENTTARGET" ;
my $ loginButton = "ctl00\$ContentPlaceHolder1\$Logincontrol1\$LoginBtn" ;
Log3 $ name , 3 , "$name - not logged in. Try again ..." ;
# Credentials abrufen
2019-03-16 06:48:59 +00:00
my ( $ success , $ username , $ password ) = getcredentials ( $ hash , 0 ) ;
2019-03-03 20:08:04 +00:00
unless ( $ success ) {
Log3 ( $ name , 1 , "$name - Credentials couldn't be retrieved successfully - make sure you've set it with \"set $name credentials <username> <password>\"" ) ;
$ login_state = 0 ;
} else {
my $ loginp = $ ua - > post ( 'https://www.sunnyportal.com/Templates/Start.aspx' , [ $ usernameField = > $ username , $ passwordField = > $ password , "__EVENTTARGET" = > $ loginButton ] ) ;
Log3 $ name , 4 , "$name -> " . $ loginp - > code ;
Log3 $ name , 5 , "$name -> Login-Page return: " . $ loginp - > content ;
if ( $ loginp - > content =~ /Logincontrol1_ErrorLabel/i ) {
Log3 $ name , 1 , "$name - Error: login to SMA-Portal failed" ;
$ livedata_content = "{\"Login-Status\":\"failed\"}" ;
} else {
2019-03-09 08:32:50 +00:00
Log3 $ name , 3 , "$name - login to SMA-Portal successful ... " ;
2019-03-03 20:08:04 +00:00
$ livedata_content = '{"Login-Status":"successful", "InfoMessages":["login to SMA-Portal successful but get data with next data cycle."]}' ;
$ login_state = 1 ;
}
my $ shmp = $ ua - > get ( 'https://www.sunnyportal.com/FixedPages/HoManLive.aspx' ) ;
Log3 $ name , 5 , "$name -> " . $ shmp - > code ;
}
}
2019-03-16 06:48:59 +00:00
my ( $ reread , $ retry ) = analivedat ( $ hash , $ livedata_content ) ;
2019-03-09 08:32:50 +00:00
2019-03-03 20:08:04 +00:00
# Daten müssen als Einzeiler zurückgegeben werden
2019-03-09 18:58:54 +00:00
$ livedata_content = encode_base64 ( $ livedata_content , "" ) ;
$ forecast_content = encode_base64 ( $ forecast_content , "" ) if ( $ forecast_content ) ;
$ weatherdata_content = encode_base64 ( $ weatherdata_content , "" ) if ( $ weatherdata_content ) ;
2019-03-03 20:08:04 +00:00
2019-03-09 18:58:54 +00:00
return "$name|$livedata_content|$forecast_content|$weatherdata_content|$login_state|$reread|$retry" ;
2019-03-03 20:08:04 +00:00
}
################################################################
## Verarbeitung empfangene Daten, setzen Readings
################################################################
2019-03-16 06:48:59 +00:00
sub ParseData ($) {
2019-03-03 20:08:04 +00:00
my ( $ string ) = @ _ ;
my @ a = split ( "\\|" , $ string ) ;
2019-03-09 18:58:54 +00:00
my $ hash = $ defs { $ a [ 0 ] } ;
my $ name = $ hash - > { NAME } ;
my $ ld_response = decode_base64 ( $ a [ 1 ] ) ;
my $ fd_response = decode_base64 ( $ a [ 2 ] ) if ( $ a [ 2 ] ) ;
my $ wd_response = decode_base64 ( $ a [ 3 ] ) if ( $ a [ 3 ] ) ;
my $ login_state = $ a [ 4 ] ;
my $ reread = $ a [ 5 ] ;
my $ retry = $ a [ 6 ] ;
my $ livedata_content = decode_json ( $ ld_response ) ;
my $ forecast_content = decode_json ( $ fd_response ) if ( $ fd_response ) ;
my $ weatherdata_content = decode_json ( $ wd_response ) if ( $ wd_response ) ;
2019-03-03 20:08:04 +00:00
2019-03-10 07:34:29 +00:00
my $ state = "ok" ;
2019-03-09 08:32:50 +00:00
my $ timeout = AttrVal ( $ name , "timeout" , 30 ) ;
if ( $ reread ) {
# login war erfolgreich, aber Daten müssen jetzt noch gelesen werden
delete ( $ hash - > { HELPER } { RUNNING_PID } ) ;
readingsSingleUpdate ( $ hash , "L1_Login-Status" , "successful" , 1 ) ;
$ hash - > { HELPER } { oldlogintime } = gettimeofday ( ) ;
2019-03-16 06:48:59 +00:00
$ hash - > { HELPER } { RUNNING_PID } = BlockingCall ( "FHEM::SMAPortal::GetData" , $ name , "FHEM::SMAPortal::ParseData" , $ timeout , "FHEM::SMAPortal::ParseAborted" , $ hash ) ;
2019-03-09 08:32:50 +00:00
$ hash - > { HELPER } { RUNNING_PID } { loglevel } = 5 if ( $ hash - > { HELPER } { RUNNING_PID } ) ; # Forum #77057
return ;
}
if ( $ retry && $ hash - > { HELPER } { RETRIES } ) {
# Livedaten konnte nicht gelesen werden, neuer Versuch zeitverzögert
delete ( $ hash - > { HELPER } { RUNNING_PID } ) ;
$ hash - > { HELPER } { RETRIES } -= 1 ;
2019-03-16 06:48:59 +00:00
InternalTimer ( gettimeofday ( ) + 5 , "FHEM::SMAPortal::retrygetdata" , $ hash , 0 ) ;
2019-03-09 08:32:50 +00:00
return ;
}
2019-03-03 20:08:04 +00:00
my $ dl = AttrVal ( $ name , "detailLevel" , 1 ) ;
2019-03-16 06:48:59 +00:00
delread ( $ hash , $ dl + 1 ) ;
2019-03-03 20:08:04 +00:00
readingsBeginUpdate ( $ hash ) ;
2019-03-09 19:40:21 +00:00
my ( $ FeedIn_done , $ GridConsumption_done , $ PV_done , $ AutarkyQuote_done , $ SelfConsumption_done ) = ( 0 , 0 , 0 , 0 , 0 ) ;
my ( $ SelfConsumptionQuote_done , $ SelfSupply_done ) = ( 0 , 0 ) ;
2019-03-03 20:08:04 +00:00
for my $ k ( keys %$ livedata_content ) {
2019-03-09 11:11:53 +00:00
my $ new_val = "" ;
2019-03-03 20:08:04 +00:00
if ( defined $ livedata_content - > { $ k } ) {
2019-03-09 18:58:54 +00:00
Log3 $ name , 4 , "$name - Livedata content \"$k\": " . ( $ livedata_content - > { $ k } ) ;
2019-03-03 20:08:04 +00:00
if ( ( $ livedata_content - > { $ k } =~ m/ARRAY/i ) || ( $ livedata_content - > { $ k } =~ m/HASH/i ) ) {
if ( $ livedata_content - > { $ k } =~ m/ARRAY/i ) {
my $ hd0 = Dumper ( $ livedata_content - > { $ k } [ 0 ] ) ;
if ( ! $ hd0 ) {
next ;
}
chomp $ hd0 ;
$ hd0 =~ s/[;']//g ;
$ hd0 = ( $ hd0 =~ /^undef$/ ) ? "none" : $ hd0 ;
2019-03-09 18:58:54 +00:00
Log3 $ name , 4 , "$name - Livedata ARRAY content \"$k\": $hd0" ;
2019-03-03 20:08:04 +00:00
$ new_val = $ hd0 ;
}
} else {
$ new_val = $ livedata_content - > { $ k } ;
}
if ( $ new_val && $ k !~ /__type/i ) {
2019-03-09 08:32:50 +00:00
Log3 $ name , 4 , "$name -> $k - $new_val" ;
2019-03-03 20:08:04 +00:00
readingsBulkUpdate ( $ hash , "L1_$k" , $ new_val ) ;
2019-03-09 11:11:53 +00:00
$ FeedIn_done = 1 if ( $ k =~ /^FeedIn$/ ) ;
$ GridConsumption_done = 1 if ( $ k =~ /^GridConsumption$/ ) ;
$ PV_done = 1 if ( $ k =~ /^PV$/ ) ;
2019-03-09 19:40:21 +00:00
$ AutarkyQuote_done = 1 if ( $ k =~ /^AutarkyQuote$/ ) ;
$ SelfConsumption_done = 1 if ( $ k =~ /^SelfConsumption$/ ) ;
$ SelfConsumptionQuote_done = 1 if ( $ k =~ /^SelfConsumptionQuote$/ ) ;
$ SelfSupply_done = 1 if ( $ k =~ /^SelfSupply$/ ) ;
2019-03-03 20:08:04 +00:00
}
}
}
2019-03-09 11:11:53 +00:00
readingsBulkUpdate ( $ hash , "L1_FeedIn" , 0 ) if ( ! $ FeedIn_done ) ;
readingsBulkUpdate ( $ hash , "L1_GridConsumption" , 0 ) if ( ! $ GridConsumption_done ) ;
readingsBulkUpdate ( $ hash , "L1_PV" , 0 ) if ( ! $ PV_done ) ;
2019-03-09 19:40:21 +00:00
readingsBulkUpdate ( $ hash , "L1_AutarkyQuote" , 0 ) if ( ! $ AutarkyQuote_done ) ;
readingsBulkUpdate ( $ hash , "L1_SelfConsumption" , 0 ) if ( ! $ SelfConsumption_done ) ;
readingsBulkUpdate ( $ hash , "L1_SelfConsumptionQuote" , 0 ) if ( ! $ SelfConsumptionQuote_done ) ;
readingsBulkUpdate ( $ hash , "L1_SelfSupply" , 0 ) if ( ! $ SelfSupply_done ) ;
2019-03-09 11:11:53 +00:00
2019-03-03 20:08:04 +00:00
readingsEndUpdate ( $ hash , 1 ) ;
if ( $ forecast_content && $ forecast_content !~ m/undefined/i ) {
# Auswertung der Forecast Daten
2019-03-16 06:48:59 +00:00
extractForecastData ( $ hash , $ forecast_content ) ;
2019-03-03 20:08:04 +00:00
}
2019-03-09 18:58:54 +00:00
if ( $ weatherdata_content && $ weatherdata_content !~ m/undefined/i ) {
# Auswertung Wetterdaten
2019-03-16 06:48:59 +00:00
extractWeatherData ( $ hash , $ weatherdata_content ) ;
2019-03-09 18:58:54 +00:00
}
2019-03-03 20:08:04 +00:00
my $ pv = ReadingsVal ( $ name , "L1_PV" , 0 ) ;
my $ fi = ReadingsVal ( $ name , "L1_FeedIn" , 0 ) ;
my $ gc = ReadingsVal ( $ name , "L1_GridConsumption" , 0 ) ;
2019-03-10 07:34:29 +00:00
my $ sum = $ fi - $ gc ;
if ( ! $ hash - > { HELPER } { RETRIES } && ! $ pv && ! $ fi && ! $ gc ) {
# keine Anlagendaten vorhanden
2019-03-10 21:57:40 +00:00
$ state = "Data can't be retrieved from SMA-Portal. It will be reread at next scheduled cycle." ;
2019-03-10 07:34:29 +00:00
Log3 $ name , 2 , "$name - $state" ;
}
2019-03-03 20:08:04 +00:00
readingsBeginUpdate ( $ hash ) ;
if ( $ login_state ) {
readingsBulkUpdate ( $ hash , "state" , $ state ) ;
2019-03-10 07:34:29 +00:00
readingsBulkUpdate ( $ hash , "summary" , $ sum ) ;
2019-03-03 20:08:04 +00:00
}
readingsEndUpdate ( $ hash , 1 ) ;
delete ( $ hash - > { HELPER } { RUNNING_PID } ) ;
}
################################################################
## Timeout BlockingCall
################################################################
2019-03-16 06:48:59 +00:00
sub ParseAborted ($) {
2019-03-03 20:08:04 +00:00
my ( $ hash , $ cause ) = @ _ ;
my $ name = $ hash - > { NAME } ;
$ cause = $ cause ? $ cause: "Timeout: process terminated" ;
Log3 ( $ name , 1 , "$name -> BlockingCall $hash->{HELPER}{RUNNING_PID}{fn} pid:$hash->{HELPER}{RUNNING_PID}{pid} $cause" ) ;
delete ( $ hash - > { HELPER } { RUNNING_PID } ) ;
}
################################################################
## regelmäßig Cookie-Datei löschen
################################################################
2019-03-16 06:48:59 +00:00
sub delcookiefile ($;$) {
2019-03-03 20:08:04 +00:00
my ( $ hash , $ must ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my ( $ validperiod , $ cookieLocation , $ oldlogintime , $ delfile ) ;
2019-03-16 06:48:59 +00:00
RemoveInternalTimer ( $ hash , "FHEM::SMAPortal::delcookiefile" ) ;
2019-03-03 20:08:04 +00:00
# Gültigkeitsdauer Cookie in Sekunden
$ validperiod = AttrVal ( $ name , "cookielifetime" , 3000 ) ;
$ cookieLocation = AttrVal ( $ name , "cookieLocation" , "./log/mycookies.txt" ) ;
if ( $ must ) {
# Cookie Zwangslöschung
$ delfile = unlink ( $ cookieLocation ) ;
}
$ oldlogintime = $ hash - > { HELPER } { oldlogintime } ? $ hash - > { HELPER } { oldlogintime } : 0 ;
if ( $ init_done == 1 ) {
# Abfrage ob gettimeofday() größer ist als gettimeofday()+$validperiod
if ( gettimeofday ( ) > $ oldlogintime + $ validperiod ) {
$ delfile = unlink ( $ cookieLocation ) ;
}
}
if ( $ delfile ) {
Log3 $ name , 3 , "$name - cookie file deleted: $cookieLocation" ;
}
return if ( IsDisabled ( $ name ) ) ;
2019-03-16 06:48:59 +00:00
InternalTimer ( gettimeofday ( ) + 30 , "FHEM::SMAPortal::delcookiefile" , $ hash , 0 ) ;
2019-03-03 20:08:04 +00:00
return ;
}
################################################################
## Auswertung Forecast Daten
################################################################
2019-03-16 06:48:59 +00:00
sub extractForecastData ($$) {
2019-03-03 20:08:04 +00:00
my ( $ hash , $ forecast ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ dl = AttrVal ( $ name , "detailLevel" , 1 ) ;
if ( $ dl <= 1 ) {
return ;
}
my ( $ sec , $ min , $ hour , $ mday , $ mon , $ year , $ wday , $ yday , $ isdst ) = localtime ( time ) ;
$ year += 1900 ;
$ mon += 1 ;
my $ today = "$year-" . sprintf ( "%02d" , $ mon ) . "-" . sprintf ( "%02d" , $ mday ) . "T" ;
my $ PV_sum = 0 ;
my $ consum_sum = 0 ;
my $ sum = 0 ;
readingsBeginUpdate ( $ hash ) ;
my $ plantOid = $ forecast - > { 'ForecastTimeframes' } - > { 'PlantOid' } ;
# Counter for forecast objects
my $ obj_nr = 0 ;
# The next few hours...
my % nextFewHoursSum = ( "PV" = > 0 , "Consumption" = > 0 , "Total" = > 0 , "ConsumpRcmd" = > 0 ) ;
# Rest of the day...
my % restOfDaySum = ( "PV" = > 0 , "Consumption" = > 0 , "Total" = > 0 , "ConsumpRcmd" = > 0 ) ;
# Tomorrow...
my % tomorrowSum = ( "PV" = > 0 , "Consumption" = > 0 , "Total" = > 0 , "ConsumpRcmd" = > 0 ) ;
# Get the current day (for 2016-02-26, this is 26)
my $ current_day = ( localtime ) [ 3 ] ;
# Loop through all forecast objects
foreach my $ fc_obj ( @ { $ forecast - > { 'ForecastSeries' } } ) {
# Log3 $name, 4, "$name - Forecast data: ".Dumper $fc_obj;
# Example for DateTime: 2016-02-15T23:00:00
my $ fc_datetime = $ fc_obj - > { 'TimeStamp' } - > { 'DateTime' } ;
# Calculate Unix timestamp (month begins at 0, year at 1900)
my ( $ fc_year , $ fc_month , $ fc_day , $ fc_hour ) = $ fc_datetime =~ /^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):00:00$/ ;
my $ fc_uts = POSIX:: mktime ( 0 , 0 , $ fc_hour , $ fc_day , $ fc_month - 1 , $ fc_year - 1900 ) ;
my $ fc_diff_seconds = $ fc_uts - time + 3600 ; # So we go above 0 for the current hour
my $ fc_diff_hours = int ( $ fc_diff_seconds / 3600 ) ;
#Log3 $hash->{NAME}, 3, "Found $fc_datetime, diff $fc_diff_seconds seconds, $fc_diff_hours hours.";
# Don't use old data
next if $ fc_diff_seconds < 0 ;
# Sum up for the next few hours (4 hours total, this is current hour plus the next 3 hours)
if ( $ obj_nr < 4 ) {
$ nextFewHoursSum { 'PV' } += $ fc_obj - > { 'PvMeanPower' } - > { 'Amount' } ;
$ nextFewHoursSum { 'Consumption' } += $ fc_obj - > { 'ConsumptionForecast' } - > { 'Amount' } / 3600 ;
$ nextFewHoursSum { 'Total' } += $ fc_obj - > { 'PvMeanPower' } - > { 'Amount' } - $ fc_obj - > { 'ConsumptionForecast' } - > { 'Amount' } / 3600 ;
$ nextFewHoursSum { 'ConsumpRcmd' } += $ fc_obj - > { 'IsConsumptionRecommended' } ? 1 : 0 ;
}
# If data is for the rest of the current day
if ( $ current_day == $ fc_day ) {
$ restOfDaySum { 'PV' } += $ fc_obj - > { 'PvMeanPower' } - > { 'Amount' } ;
$ restOfDaySum { 'Consumption' } += $ fc_obj - > { 'ConsumptionForecast' } - > { 'Amount' } / 3600 ;
$ restOfDaySum { 'Total' } += $ fc_obj - > { 'PvMeanPower' } - > { 'Amount' } - $ fc_obj - > { 'ConsumptionForecast' } - > { 'Amount' } / 3600 ;
$ restOfDaySum { 'ConsumpRcmd' } += $ fc_obj - > { 'IsConsumptionRecommended' } ? 1 : 0 ;
}
# If data is for the next day (quick and dirty: current day different from this object's day)
# Assuming only the current day and the next day are returned from Sunny Portal
if ( $ current_day != $ fc_day ) {
$ tomorrowSum { 'PV' } += $ fc_obj - > { 'PvMeanPower' } - > { 'Amount' } if ( exists ( $ fc_obj - > { 'PvMeanPower' } - > { 'Amount' } ) ) ;
$ tomorrowSum { 'Consumption' } += $ fc_obj - > { 'ConsumptionForecast' } - > { 'Amount' } / 3600 ;
$ tomorrowSum { 'Total' } += $ fc_obj - > { 'PvMeanPower' } - > { 'Amount' } - $ fc_obj - > { 'ConsumptionForecast' } - > { 'Amount' } / 3600 if ( $ fc_obj - > { 'PvMeanPower' } - > { 'Amount' } ) ;
$ tomorrowSum { 'ConsumpRcmd' } += $ fc_obj - > { 'IsConsumptionRecommended' } ? 1 : 0 ;
}
# Update values in Fhem if less than 24 hours in the future
if ( $ dl >= 4 ) {
if ( $ obj_nr < 24 ) {
my $ time_str = "ThisHour" ;
$ time_str = "NextHour" . sprintf ( "%02d" , $ obj_nr ) if ( $ fc_diff_hours > 0 ) ;
readingsBulkUpdate ( $ hash , "L4_${time_str}_Time" , $ fc_obj - > { 'TimeStamp' } - > { 'DateTime' } ) ;
readingsBulkUpdate ( $ hash , "L4_${time_str}_PvMeanPower" , int ( $ fc_obj - > { 'PvMeanPower' } - > { 'Amount' } ) ) ;
readingsBulkUpdate ( $ hash , "L4_${time_str}_Consumption" , int ( $ fc_obj - > { 'ConsumptionForecast' } - > { 'Amount' } / 3600 ) ) ;
readingsBulkUpdate ( $ hash , "L4_${time_str}_IsConsumptionRecommended" , ( $ fc_obj - > { 'IsConsumptionRecommended' } ? "yes" : "no" ) ) ;
readingsBulkUpdate ( $ hash , "L4_${time_str}" , int ( $ fc_obj - > { 'PvMeanPower' } - > { 'Amount' } - $ fc_obj - > { 'ConsumptionForecast' } - > { 'Amount' } / 3600 ) ) ;
}
}
# Increment object counter
$ obj_nr + + ;
}
if ( $ dl >= 2 ) {
readingsBulkUpdate ( $ hash , "L2_Next04Hours-Consumption" , int ( $ nextFewHoursSum { 'Consumption' } ) ) ;
readingsBulkUpdate ( $ hash , "L2_Next04Hours-PV" , int ( $ nextFewHoursSum { 'PV' } ) ) ;
readingsBulkUpdate ( $ hash , "L2_Next04Hours-Total" , int ( $ nextFewHoursSum { 'Total' } ) ) ;
readingsBulkUpdate ( $ hash , "L2_Next04Hours-IsConsumptionRecommended" , int ( $ nextFewHoursSum { 'ConsumpRcmd' } ) ) ;
readingsBulkUpdate ( $ hash , "next04hours_state" , int ( $ nextFewHoursSum { 'PV' } ) ) ;
}
if ( $ dl >= 3 ) {
readingsBulkUpdate ( $ hash , "L3_RestOfDay-Consumption" , int ( $ restOfDaySum { 'Consumption' } ) ) ;
readingsBulkUpdate ( $ hash , "L3_RestOfDay-PV" , int ( $ restOfDaySum { 'PV' } ) ) ;
readingsBulkUpdate ( $ hash , "L3_RestOfDay-Total" , int ( $ restOfDaySum { 'Total' } ) ) ;
readingsBulkUpdate ( $ hash , "L3_RestOfDay-IsConsumptionRecommended" , int ( $ restOfDaySum { 'ConsumpRcmd' } ) ) ;
readingsBulkUpdate ( $ hash , "L3_Tomorrow-Consumption" , int ( $ tomorrowSum { 'Consumption' } ) ) ;
readingsBulkUpdate ( $ hash , "L3_Tomorrow-PV" , int ( $ tomorrowSum { 'PV' } ) ) ;
readingsBulkUpdate ( $ hash , "L3_Tomorrow-Total" , int ( $ tomorrowSum { 'Total' } ) ) ;
readingsBulkUpdate ( $ hash , "L3_Tomorrow-IsConsumptionRecommended" , int ( $ tomorrowSum { 'ConsumpRcmd' } ) ) ;
}
if ( $ dl >= 4 ) {
readingsBulkUpdate ( $ hash , "L4_plantOid" , $ plantOid ) ;
}
readingsEndUpdate ( $ hash , 1 ) ;
return ;
}
2019-03-09 18:58:54 +00:00
################################################################
## Auswertung Wetterdaten
################################################################
2019-03-16 06:48:59 +00:00
sub extractWeatherData ($$) {
2019-03-09 18:58:54 +00:00
my ( $ hash , $ weather ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ dl = AttrVal ( $ name , "detailLevel" , 1 ) ;
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/i ) {
my $ ih = $ weather - > { $ k } ;
for my $ i ( keys %$ ih ) {
my $ hd0 = Dumper ( $ weather - > { $ k } { $ i } ) ;
if ( ! $ hd0 ) {
next ;
}
chomp $ hd0 ;
$ hd0 =~ s/[;']//g ;
$ hd0 = ( $ hd0 =~ /^undef$/ ) ? "none" : $ hd0 ;
Log3 $ name , 4 , "$name - Weatherdata HASH content \"$k $i\": $hd0" ;
next if ( $ i =~ /^WeatherIcon$/ ) ;
$ new_val = $ hd0 ;
if ( $ new_val ) {
$ new_val =~ s/.*}([A-Z]).*/°$1/ if ( $ i =~ /^TemperatureSymbol$/ ) ;
$ new_val = sprintf ( "%.1f" , $ new_val ) if ( $ i =~ /^Temperature$/ ) ;
Log3 $ name , 4 , "$name -> ${k}_${i} - $new_val" ;
readingsBulkUpdate ( $ hash , "L1_${k}_${i}" , $ new_val ) ;
}
}
}
}
}
readingsEndUpdate ( $ hash , 1 ) ;
return ;
}
2019-03-03 20:08:04 +00:00
################################################################
# sortiert eine Liste von Versionsnummern x.x.x
# Schwartzian Transform and the GRT transform
# Übergabe: "asc | desc",<Liste von Versionsnummern>
################################################################
2019-03-16 06:48:59 +00:00
sub sortVersionNum (@) {
2019-03-03 20:08:04 +00:00
my ( $ sseq , @ versions ) = @ _ ;
my @ sorted = map { $ _ - > [ 0 ] }
sort { $ a - > [ 1 ] cmp $ b - > [ 1 ] }
map { [ $ _ , pack "C*" , split /\./ ] } @ versions ;
@ sorted = map { join "." , unpack "C*" , $ _ }
sort
map { pack "C*" , split /\./ } @ versions ;
if ( $ sseq eq "desc" ) {
@ sorted = reverse @ sorted ;
}
return @ sorted ;
}
2019-03-16 06:48:59 +00:00
################################################################
# Versionierungen des Moduls setzen
# Die Verwendung von Meta.pm und Packages wird berücksichtigt
################################################################
sub setVersionInfo ($) {
2019-03-18 23:51:42 +00:00
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
2019-03-19 18:11:34 +00:00
my $ v = ( sortTopicNum ( "desc" , keys % vNotesIntern ) ) [ 0 ] ;
my $ type = $ hash - > { TYPE } ;
2019-03-18 23:51:42 +00:00
$ hash - > { HELPER } { PACKAGE } = __PACKAGE__ ;
2019-03-19 18:11:34 +00:00
$ hash - > { HELPER } { VERSION } = $ v ;
2019-03-16 06:48:59 +00:00
if ( $ modules { $ type } { META } { x_prereqs_src } ) {
# META-Daten sind vorhanden
2019-03-19 18:11:34 +00:00
$ modules { $ type } { META } { version } = "v" . $ v ; # Version aus META.json überschreiben, Anzeige mit {Dumper $modules{SMAPortal}{META}}
2019-03-16 06:48:59 +00:00
if ( $ modules { $ type } { META } { x_version } ) { # {x_version} ( nur gesetzt wenn $Id: ... $ im Kopf komplett! vorhanden )
2019-03-19 18:11:34 +00:00
$ modules { $ type } { META } { x_version } =~ s/1.1.1/$v/g ;
2019-03-16 06:48:59 +00:00
} else {
2019-03-19 18:11:34 +00:00
$ modules { $ type } { META } { x_version } = $ v ;
2019-03-16 06:48:59 +00:00
}
return $@ unless ( FHEM::Meta:: SetInternals ( $ hash ) ) ; # FVERSION wird gesetzt ( nur gesetzt wenn $Id: ... $ im Kopf komplett! vorhanden )
2019-03-19 18:11:34 +00:00
if ( __PACKAGE__ eq $ type ) {
2019-03-16 06:48:59 +00:00
# es wird mit Packages gearbeitet -> Perl übliche Modulversion setzen
2019-03-19 18:11:34 +00:00
# mit {<Modul>->VERSION()} im FHEMWEB kann Modulversion abgefragt werden
2019-03-16 06:48:59 +00:00
use version 0.77 ; our $ VERSION = FHEM::Meta:: Get ( $ hash , 'version' ) ;
}
} else {
# herkömmliche Modulstruktur
2019-03-19 18:11:34 +00:00
$ hash - > { VERSION } = $ v ;
2019-03-16 06:48:59 +00:00
}
return ;
}
2019-03-03 20:08:04 +00:00
################################################################
# delete Readings
# $dl = detailLevel ab dem das Reading gelöscht werden soll
################################################################
2019-03-16 06:48:59 +00:00
sub delread ($;$) {
2019-03-03 20:08:04 +00:00
my ( $ hash , $ dl ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my @ allrds = keys % { $ defs { $ name } { READINGS } } ;
if ( $ dl ) {
# Readings ab dem angegebenen Detail-Level löschen
foreach my $ key ( @ allrds ) {
$ key =~ m/^L(\d)_.*$/ ;
if ( $ 1 && $ 1 >= $ dl ) {
delete ( $ defs { $ name } { READINGS } { $ key } ) ;
}
}
return ;
}
foreach my $ key ( @ allrds ) {
delete ( $ defs { $ name } { READINGS } { $ key } ) if ( $ key ne "state" ) ;
}
return ;
}
2019-03-09 08:32:50 +00:00
################################################################
# analysiere Livedaten
################################################################
2019-03-16 06:48:59 +00:00
sub analivedat ($$) {
2019-03-09 08:32:50 +00:00
my ( $ hash , $ lc ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my ( $ reread , $ retry ) = ( 0 , 0 ) ;
my $ livedata_content = decode_json ( $ lc ) ;
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/i ) ) {
if ( $ livedata_content - > { $ k } =~ m/ARRAY/i ) {
my $ hd0 = Dumper ( $ livedata_content - > { $ k } [ 0 ] ) ;
if ( ! $ hd0 ) {
next ;
}
chomp $ hd0 ;
$ hd0 =~ s/[;']//g ;
$ hd0 = ( $ hd0 =~ /^undef$/ ) ? "none" : $ hd0 ;
Log3 $ name , 4 , "$name - livedata ARRAY content \"$k\": $hd0" ;
$ new_val = $ hd0 ;
}
} else {
$ new_val = $ livedata_content - > { $ k } ;
}
if ( $ new_val && $ k !~ /__type/i ) {
if ( $ k =~ /InfoMessages/ && $ new_val =~ /.*login to SMA-Portal successful.*/ ) {
# Login war erfolgreich, Daten neu lesen
Log3 $ name , 3 , "$name - get data again" ;
$ reread = 1 ;
}
if ( $ k =~ /ErrorMessages/ && $ new_val =~ /.*The current data cannot be retrieved from the PV system. Check the cabling and configuration of the following energy meters.*/ ) {
# Energiedaten konnten nicht ermittelt werden, Daten neu lesen mit Zeitverzögerung
2019-03-09 18:58:54 +00:00
Log3 $ name , 3 , "$name - The current data cannot be retrieved from PV system, get data again." ;
2019-03-09 08:32:50 +00:00
$ retry = 1 ;
}
2019-03-11 22:54:31 +00:00
if ( $ k =~ /ErrorMessages/ && $ new_val =~ /.*Communication with the Sunny Home Manager is currently not possible.*/ ) {
# Energiedaten konnten nicht ermittelt werden, Daten neu lesen mit Zeitverzögerung
Log3 $ name , 3 , "$name - Communication with the Sunny Home Manager currently impossible, get data again." ;
$ retry = 1 ;
}
2019-03-09 08:32:50 +00:00
}
}
}
return ( $ reread , $ retry ) ;
}
################################################################
# Restart get Data
################################################################
2019-03-16 06:48:59 +00:00
sub retrygetdata ($) {
2019-03-09 08:32:50 +00:00
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ timeout = AttrVal ( $ name , "timeout" , 30 ) ;
2019-03-16 06:48:59 +00:00
$ hash - > { HELPER } { RUNNING_PID } = BlockingCall ( "FHEM::SMAPortal::GetData" , $ name , "FHEM::SMAPortal::ParseData" , $ timeout , "FHEM::SMAPortal::ParseAborted" , $ hash ) ;
2019-03-09 08:32:50 +00:00
$ hash - > { HELPER } { RUNNING_PID } { loglevel } = 5 if ( $ hash - > { HELPER } { RUNNING_PID } ) ; # Forum #77057
return ;
}
2019-03-03 20:08:04 +00:00
1 ;
= pod
2019-03-11 22:54:31 +00:00
= encoding utf8
2019-03-12 22:17:06 +00:00
= item summary Module for communication with the SMA - Portal
2019-03-11 22:54:31 +00:00
= item summary_DE Modul zur Kommunikation mit dem SMA - Portal
2019-03-03 20:08:04 +00:00
= begin html
< a name = "SMAPortal" > </a>
<h3> SMAPortal </h3>
<ul>
<br>
Is coming soon ...
</ul>
= end html
= begin html_DE
< a name = "SMAPortal" > </a>
<h3> SMAPortal </h3>
<ul>
Mit diesem Modul können Daten aus dem < a href = "https://www.sunnyportal.com" > SMA - Portal </a> abgerufen werden .
Momentan sind es: <br> <br>
<ul>
<ul>
<li> Live - Daten ( Verbrauch und PV - Erzeugung ) </li>
2019-03-10 07:34:29 +00:00
<li> Wetter - Daten von SMA für den Anlagenstandort </li>
2019-03-03 20:08:04 +00:00
<li> Prognosedaten ( Verbrauch und PV - Erzeugung ) inklusive Verbraucherempfehlung </li>
</ul>
</ul>
<br>
<b> Vorbereitung </b> <br> <br>
<ul>
Dieses Modul nutzt das Perl - Modul JSON welches üblicherweise nachinstalliert werden muss . <br>
Auf Debian - Linux basierenden Systemen kann es installiert werden mit: <br> <br>
<code> sudo apt - get install libjson - perl </code> <br> <br>
Überblick über die Perl - Module welche von SMAPortal genutzt werden: <br> <br>
POSIX <br>
JSON <br>
Data:: Dumper <br>
Time:: HiRes <br>
Blocking ( FHEM - Modul ) <br>
LWP:: UserAgent <br>
HTTP:: Cookies
<br> <br>
</ul>
< a name = "SMAPortalDefine" > </a>
<b> Definition </b>
<ul>
<br>
Ein SMAPortal - Device wird definiert mit: <br> <br>
<ul>
<b> <code> define & lt ; Name & gt ; SMAPortal </code> </b> <br> <br>
</ul>
Nach der Definition des Devices müssen noch die Zugangsparameter für das SMA - Portal gespeichert werden .
Das geschieht mit dem Befehl: <br> <br>
<ul>
set & lt ; Name & gt ; credentials & lt ; Username & gt ; & lt ; Passwort & gt ;
</ul>
</ul>
<br> <br>
< a name = "SMAPortalSet" > </a>
<b> Set </b>
<ul>
<br>
<ul>
<li> <b> set & lt ; name & gt ; credentials & lt ; username & gt ; & lt ; password & gt ; </b> </li>
Setzt Username / Passwort für den Zugriff zum SMA - Portal .
</ul>
</ul>
<br> <br>
< a name = "SMAPortalGet" > </a>
<b> Get </b>
<ul>
<br>
<ul>
<li> <b> get & lt ; name & gt ; data </b> </li>
Mit diesem Befehl werden die Daten aus dem SMA - Portal manuell abgerufen .
</ul>
<br>
<ul>
<li> <b> get & lt ; name & gt ; storedCredentials </b> </li>
Die gespeicherten Anmeldeinformationen ( Credentials ) werden in einem Popup als Klartext angezeigt .
</ul>
</ul>
<br> <br>
< a name = "SMAPortalAttr" > </a>
<b> Attribute </b>
<ul>
<br>
<ul>
< a name = "cookielifetime" > </a>
<li> <b> cookielifetime & lt ; Sekunden & gt ; </b> <br>
Gültigkeitszeitraum für einen empfangenen Cookie ( Default: 3000 Sekunden ) .
</li> <br>
< a name = "cookieLocation" > </a>
<li> <b> cookieLocation & lt ; Pfad /File> </ b > <br>
Angabe von Pfad und Datei zur Abspeicherung des empfangenen Cookies ( Default: . /log/m ycookies . txt ) .
<br> <br>
<ul>
<b> Beispiel: </b> <br>
attr & lt ; name & gt ; cookieLocation . /log/coo kies . txt <br>
</ul>
</li> <br>
< a name = "detailLevel" > </a>
<li> <b> detailLevel </b> <br>
Es wird der Umfang der zu generierenden Daten eingestellt .
<br> <br>
<ul>
<table>
<colgroup> < col width = 5 % > < col width = 95 % > < / colgroup >
2019-03-10 07:34:29 +00:00
<tr> <td> <b> L1 </b> </td> <td> - nur Live - Daten und Wetter - Daten werden generiert . </td> </tr>
<tr> <td> <b> L2 </b> </td> <td> - wie L1 und zusätzlich Prognose der nächsten 4 Stunden </td> </tr>
2019-03-09 08:32:50 +00:00
<tr> <td> <b> L3 </b> </td> <td> - wie L2 und zusätzlich Prognosedaten des Resttages und Folgetages </td> </tr>
2019-03-03 20:08:04 +00:00
<tr> <td> <b> L4 </b> </td> <td> - wie L3 und zusätzlich die detaillierte Prognose der nächsten 24 Stunden </td> </tr>
</table>
</ul>
<br>
</li> <br>
< a name = "disable" > </a>
<li> <b> disable </b> <br>
Deaktiviert das Device . </li> <br>
2019-03-09 08:32:50 +00:00
< a name = "getDataRetries" > </a>
<li> <b> getDataRetries & lt ; Anzahl & gt ; </b> <br>
Anzahl der Wiederholungen ( get data ) im Fall dass keine Live - Daten vom SMA - Portal geliefert
wurden ( default: 3 ) . </li> <br>
2019-03-03 20:08:04 +00:00
< a name = "interval" > </a>
<li> <b> interval & lt ; Sekunden & gt ; </b> <br>
Zeitintervall zum kontinuierlichen Datenabruf aus dem SMA - Portal ( Default: 300 Sekunden ) . <br>
Ist "interval = 0" gesetzt , erfolgt kein automatischer Datenabruf und muss mit "get <name> data" manuell
erfolgen . </li> <br>
< a name = "showPassInLog" > </a>
<li> <b> showPassInLog </b> <br>
Wenn gesetzt , wird das verwendete Passwort im Logfile angezeigt .
( default = 0 ) </li> <br>
< a name = "timeout" > </a>
<li> <b> timeout & lt ; Sekunden & gt ; </b> <br>
Timeout - Wert für HTTP - Aufrufe zum SMA - Portal ( Default: 30 Sekunden ) .
</li> <br>
< a name = "userAgent" > </a>
<li> <b> userAgent & lt ; Kennung & gt ; </b> <br>
Es kann die User - Agent - Kennung zur Identifikation gegenüber dem Portal angegeben werden .
<br> <br>
<ul>
<b> Beispiel: </b> <br>
attr & lt ; name & gt ; userAgent Mozilla /5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/ 20100101 Firefox / 65.0 <br>
</ul>
</li> <br>
</ul>
</ul>
2019-03-12 22:17:06 +00:00
</ul>
= end html_DE
= for : application / json ; q = META . json 76 _SMAPortal . pm
{
"abstract" : "Module for communication with the SMA-Portal" ,
"x_lang" : {
"de" : {
"abstract" : "Modul zur Kommunikation mit dem SMA-Portal"
}
} ,
"keywords" : [
"sma" ,
"photovoltaik" ,
"electricity" ,
"portal" ,
"smaportal"
] ,
2019-03-18 23:51:42 +00:00
"version" : "v1.1.1" ,
2019-03-12 22:17:06 +00:00
"release_status" : "testing" ,
"author" : [
"Heiko Maaz <heiko.maaz@t-online.de>"
] ,
"x_fhem_maintainer" : [
"DS_Starter"
] ,
"x_fhem_maintainer_github" : [
"nasseeder1"
] ,
"prereqs" : {
"runtime" : {
"requires" : {
"FHEM" : 5.00918799 ,
"perl" : 5.014 ,
"JSON" : 0 ,
"POSIX" : 0 ,
"Data::Dumper" : 0 ,
2019-03-16 06:48:59 +00:00
"Blocking" : 0 ,
"GPUtils" : 0 ,
"Time::HiRes" : 0 ,
"LWP" : 0 ,
"HTTP::Cookies" : 0 ,
"FHEM::Meta" : 0 ,
"MIME::Base64" : 0
2019-03-12 22:17:06 +00:00
} ,
"recommends" : {
} ,
"suggests" : {
}
}
} ,
"resources" : {
"bugtracker" : {
"web" : "https://forum.fhem.de/index.php/board,29.0.html" ,
"x_web_title" : "FHEM Forum: Sonstige Systeme"
}
}
}
= end : application / json ; q = META . json
= cut