2019-03-03 20:08:04 +00:00
#########################################################################################################################
2020-12-02 09:37:43 +00:00
# $Id: 76_SMAPortal.pm 23272 2020-12-01 20:51:52Z DS_Starter $
2019-03-03 20:08:04 +00:00
#########################################################################################################################
# 76_SMAPortal.pm
#
2020-04-20 14:55:36 +00:00
# (c) 2019-2020 by Heiko Maaz
2019-03-03 20:08:04 +00:00
# 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/>.
#
2019-05-30 07:44:31 +00:00
# Credits (Thanks to all!):
# Brun von der Gönne <brun at goenne dot de> : author of 98_SHM.pm
2020-06-01 15:02:28 +00:00
# BerndArnold : author of 98_SHMForecastRelative.pm / add get statistic data
2019-05-30 07:44:31 +00:00
# Wzut/XGuide : creation of SMAPortal graphics
2019-03-03 20:08:04 +00:00
#
# FHEM Forum: http://forum.fhem.de/index.php/topic,27667.0.html
#
#########################################################################################################################
#
# Definition: define <name> SMAPortal
#
#########################################################################################################################
2020-04-20 14:55:36 +00:00
package FHEM::SMAPortal ; ## no critic 'package'
2019-03-03 20:08:04 +00:00
use strict ;
use warnings ;
2020-04-20 14:55:36 +00:00
use GPUtils qw( GP_Import GP_Export ) ; # wird für den Import der FHEM Funktionen aus der fhem.pl benötigt
2019-03-16 06:48:59 +00:00
use POSIX ;
2020-04-20 14:55:36 +00:00
eval "use FHEM::Meta;1" or my $ modMetaAbsent = 1 ; ## no critic 'eval'
2019-03-16 06:48:59 +00:00
use Data::Dumper ;
use Blocking ;
2020-05-31 08:53:05 +00:00
use Time::HiRes qw( gettimeofday time sleep ) ;
2019-03-24 15:51:33 +00:00
use Time::Local ;
2019-03-16 06:48:59 +00:00
use LWP::UserAgent ;
use HTTP::Cookies ;
use JSON qw( decode_json ) ;
use MIME::Base64 ;
2019-03-24 15:51:33 +00:00
use Encode ;
2020-06-20 08:02:04 +00:00
use utf8 ;
2019-03-16 06:48:59 +00:00
# Run before module compilation
BEGIN {
# Import from main::
GP_Import (
qw(
attr
2020-04-20 14:55:36 +00:00
AnalyzePerlCommand
2019-03-16 06:48:59 +00:00
AttrVal
2020-04-20 14:55:36 +00:00
AttrNum
2019-03-16 06:48:59 +00:00
addToDevAttrList
addToAttrList
BlockingCall
BlockingKill
2019-06-12 21:29:10 +00:00
BlockingInformParent
2019-03-16 06:48:59 +00:00
CommandAttr
2019-04-29 22:07:15 +00:00
CommandDefine
2019-03-16 06:48:59 +00:00
CommandDeleteAttr
CommandDeleteReading
CommandSet
2020-08-08 12:22:12 +00:00
CommandGet
2019-03-16 06:48:59 +00:00
defs
delFromDevAttrList
delFromAttrList
devspec2array
deviceEvents
Debug
FmtDateTime
FmtTime
2020-05-26 15:31:05 +00:00
fhemTzOffset
2019-05-01 14:30:09 +00:00
FW_makeImage
2019-03-16 06:48:59 +00:00
fhemTimeGm
2020-06-09 07:18:56 +00:00
fhemTimeLocal
2019-03-16 06:48:59 +00:00
getKeyValue
gettimeofday
genUUID
init_done
InternalTimer
IsDisabled
Log
2019-07-01 16:33:43 +00:00
Log3
makeReadingName
2020-11-17 16:48:00 +00:00
modules
2019-03-16 06:48:59 +00:00
readingsSingleUpdate
readingsBulkUpdate
readingsBulkUpdateIfChanged
readingsBeginUpdate
2019-03-26 17:40:00 +00:00
readingsDelete
2019-03-16 06:48:59 +00:00
readingsEndUpdate
2019-04-29 22:07:15 +00:00
ReadingsNum
2019-06-03 23:00:53 +00:00
ReadingsTimestamp
2019-03-16 06:48:59 +00:00
ReadingsVal
RemoveInternalTimer
2020-04-20 14:55:36 +00:00
readingFnAttributes
2019-03-16 06:48:59 +00:00
setKeyValue
2019-03-19 18:11:34 +00:00
sortTopicNum
2019-03-16 06:48:59 +00:00
TimeNow
Value
2019-05-30 07:44:31 +00:00
json2nameValue
2019-06-12 21:29:10 +00:00
FW_cmd
2019-06-03 23:00:53 +00:00
FW_directNotify
FW_ME
FW_subdir
FW_room
FW_detail
2020-11-17 16:48:00 +00:00
FW_wname
2019-03-16 06:48:59 +00:00
)
) ;
2020-04-20 14:55:36 +00:00
# Export to main context with different name
# my $pkg = caller(0);
# my $main = $pkg;
# $main =~ s/^(?:.+::)?([^:]+)$/main::$1\_/gx;
2020-06-03 13:33:11 +00:00
# for (@_) {
2020-04-20 14:55:36 +00:00
# *{ $main . $_ } = *{ $pkg . '::' . $_ };
# }
GP_Export (
qw(
Initialize
)
) ;
2019-03-16 06:48:59 +00:00
}
# Versions History intern
2020-04-20 14:55:36 +00:00
my % vNotesIntern = (
2020-12-02 09:37:43 +00:00
"3.7.2" = > "02.12.2020 fix PlantOid= in _getConsumerxxxData, add SUSyID 425 " ,
2020-12-01 20:51:52 +00:00
"3.7.1" = > "01.12.2020 fix problem that 'Bad request' after call consumerMasterdata if haven't any consumer available delivers an error " ,
"3.7.0" = > "21.11.2020 add new consumer management for switched sockets and SMA EV Charger " ,
2020-11-17 16:48:00 +00:00
"3.6.5" = > "12.11.2020 verbose5data switchConsumer, more preselected user agents " ,
2020-11-11 16:34:09 +00:00
"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 " ,
2020-11-04 16:55:41 +00:00
"3.6.2" = > "03.11.2020 new function _detailViewOn to Switch the detail view on SMA energy balance site, new default userAgent " ,
2020-10-31 16:17:33 +00:00
"3.6.1" = > "31.10.2020 adjust anchortime in getBalanceMonthData " ,
2020-10-31 15:32:58 +00:00
"3.6.0" = > "11.10.2020 new relative time arguments for attr balanceDay, balanceMonth, balanceYear, new attribute useRelativeNames " ,
2020-10-10 19:05:38 +00:00
"3.5.0" = > "10.10.2020 _getLiveData: get data from Dashboard instead of homemanager site depending of attr noHomeManager, " .
2020-09-30 18:35:10 +00:00
"extract OperationHealth key, new attr cookieDelete " ,
2020-08-18 19:27:12 +00:00
"3.4.1" = > "18.08.2020 add selected providerlevel to deletion blacklist # Forum: https://forum.fhem.de/index.php/topic,102112.msg1078990.html#msg1078990 " ,
2020-08-09 07:32:15 +00:00
"3.4.0" = > "09.08.2020 attr balanceDay, balanceMonth, balanceYear for data provider balanceDayData, balanceMonthData, balanceYearData " .
"set getData command, update button in header of PortalAsHtml, minor code changes according PBP" ,
2020-08-06 19:39:34 +00:00
"3.3.4" = > "12.07.2020 fix break in header if attribute hourCount was reduced " ,
2020-07-12 06:09:34 +00:00
"3.3.3" = > "07.07.2020 change extractLiveData, minor fixes " ,
2020-07-06 21:31:24 +00:00
"3.3.2" = > "05.07.2020 change timeout calc, new reading lastSuccessTime " ,
2020-07-03 07:17:50 +00:00
"3.3.1" = > "03.07.2020 change retry repetition and new cycle wait time " ,
2020-07-02 19:45:08 +00:00
"3.3.0" = > "02.07.2020 fix typo, new attribute noHomeManager " ,
"3.2.0" = > "30.06.2020 add data provider balanceCurrentData (experimental), balanceMonthData, balanceYearData " ,
2020-06-26 16:52:36 +00:00
"3.1.2" = > "25.06.2020 don't delete cookie after every data retrieval, change login management " ,
2020-06-24 16:24:42 +00:00
"3.1.1" = > "24.06.2020 change german Error regex, get plantOid from cookie if not in JSON " ,
2020-06-20 11:18:13 +00:00
"3.1.0" = > "20.06.2020 language of SMA Portal messages depend on global language attribute, avoid order problems by " .
"executing retrieve master data firstly every time" ,
2020-06-20 08:02:04 +00:00
"3.0.0" = > "18.06.2020 refactored readings and subroutines, detailLevel deleted, new attr providerLevel, integrate logbook data " ,
2020-06-17 09:16:57 +00:00
"2.10.6" = > "12.06.2020 add hash dataprovider " ,
2020-06-12 06:54:53 +00:00
"2.10.5" = > "12.06.2020 add check login by /Templates/, deeper/mulitple choice verbose5Data " ,
2020-06-11 16:17:20 +00:00
"2.10.4" = > "11.06.2020 additional L1 Readings for Battery and more " ,
2020-06-11 12:18:13 +00:00
"2.10.3" = > "11.06.2020 internal code changes, bug fixes show weather_icon " ,
2020-06-10 15:12:41 +00:00
"2.10.2" = > "10.06.2020 bug fixes get/switch consumers " ,
2020-06-09 07:18:56 +00:00
"2.10.1" = > "08.06.2020 internal code changes, bug fixes " ,
2020-06-03 19:30:51 +00:00
"2.10.0" = > "03.06.2020 refactored login process " ,
2020-06-01 15:02:28 +00:00
"2.9.0" = > "01.06.2020 add get today statistic data " ,
2020-05-31 16:03:46 +00:00
"2.8.1" = > "31.05.2020 attribute timeout, maxCallCycle deleted " ,
2020-06-03 19:30:51 +00:00
"2.8.0" = > "31.05.2020 refactored process logic, attribute cookielifetime & getDataRetries deleted, command delCookieFile deleted " .
2020-05-30 20:51:54 +00:00
"new attribute maxCallCycle " ,
2020-05-29 13:30:24 +00:00
"2.7.2" = > "28.05.2020 delete cookie file if threshold of read retries reached " ,
2020-05-28 11:40:33 +00:00
"2.7.1" = > "28.05.2020 change cookie default location to ./log/<name>_cookie.txt " ,
2020-05-28 06:52:47 +00:00
"2.7.0" = > "27.05.2020 improve stability of data retrieval, new command delCookieFile, new readings dailyCallCounter and dailyIssueCookieCounter " .
2020-05-28 07:58:16 +00:00
"current PV generation and consumption available in SMA graphics, some more improvements " ,
2020-04-21 12:34:12 +00:00
"2.6.1" = > "21.04.2020 update time in portalgraphics changed to last successful live data retrieval, credentials are not shown in list device " ,
2020-04-20 14:55:36 +00:00
"2.6.0" = > "20.04.2020 change package config, improve cookie management, decouple switch consumers from livedata retrieval " .
"some improvements according to PBP " ,
"2.5.0" = > "25.08.2019 change switch consumer to on<->automatic only in graphic overview, Forum: https://forum.fhem.de/index.php/topic,102112.msg969002.html#msg969002" ,
2019-03-16 06:48:59 +00:00
"1.0.0" = > "03.03.2019 initial "
) ;
2020-04-20 14:55:36 +00:00
2020-05-30 20:51:54 +00:00
# Voreinstellungen
2020-07-06 21:31:24 +00:00
my $ maxretries = 6 ; # max. Anzahl Wiederholungen in einem Abruf-Zyklus
2020-06-26 16:52:36 +00:00
my $ thold = int ( $ maxretries /2); # Schwellenwert nicht erfolgreicher Leseversuche in einem Zyklus mit dem gleichen Cookie, Standard: int($maxretries/ 2 )
2020-09-30 18:35:10 +00:00
my $ sleepretry = 60 ; # Sleep zwischen Data Call Retries
my $ sleepexc = 90 ; # Sleep vor neuem Cycle
2020-07-06 21:31:24 +00:00
my $ defmaxcycles = 10 ; # Standard max. Anzahl Datenabrufzyklen
2020-05-30 20:51:54 +00:00
2020-06-03 13:33:11 +00:00
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 ,
) ;
2020-11-11 16:34:09 +00:00
my % hset = ( # Hash der Set-Funktion
credentials = > { fn = > \ & _setCredentials } ,
getData = > { fn = > \ & _setGetData } ,
createPortalGraphic = > { fn = > \ & _setCreatePortalGraphic } ,
2020-08-09 07:32:15 +00:00
) ;
2020-07-03 07:17:50 +00:00
my % mandatory ; # Arbeitskopie von %stpl -> abzurufenden Datenprovider Stammdaten nach Login
my % subs ; # Arbeitskopie von %stpl -> Festlegung abzurufenden Datenprovider
my % stpl = ( # Ausgangstemplate Subfunktionen der Datenprovider
consumerMasterdata = > { doit = > 1 , nohm = > 1 , level = > 'L00' , func = > '_getConsumerMasterdata' } , # mandatory (außer wenn kein SMA Home Manager vorhanden)
plantMasterData = > { doit = > 1 , nohm = > 1 , level = > 'L00' , func = > '_getPlantMasterData' } , # mandatory (außer wenn kein SMA Home Manager vorhanden)
liveData = > { doit = > 0 , nohm = > 0 , level = > 'L01' , func = > '_getLiveData' } ,
weatherData = > { doit = > 0 , nohm = > 0 , level = > 'L02' , func = > '_getWeatherData' } ,
forecastData = > { doit = > 0 , nohm = > 1 , level = > 'L04' , func = > '_getForecastData' } ,
consumerCurrentdata = > { doit = > 0 , nohm = > 1 , level = > 'L05' , func = > '_getConsumerCurrData' } ,
consumerDayData = > { doit = > 0 , nohm = > 1 , level = > 'L06' , func = > '_getConsumerDayData' } ,
consumerMonthData = > { doit = > 0 , nohm = > 1 , level = > 'L07' , func = > '_getConsumerMonthData' } ,
consumerYearData = > { doit = > 0 , nohm = > 1 , level = > 'L08' , func = > '_getConsumerYearData' } ,
plantLogbook = > { doit = > 0 , nohm = > 0 , level = > 'L09' , func = > '_getPlantLogbook' } ,
balanceDayData = > { doit = > 0 , nohm = > 0 , level = > 'L11' , func = > '_getBalanceDayData' } ,
balanceMonthData = > { doit = > 0 , nohm = > 0 , level = > 'L12' , func = > '_getBalanceMonthData' } ,
balanceYearData = > { doit = > 0 , nohm = > 0 , level = > 'L13' , func = > '_getBalanceYearData' } ,
balanceTotalData = > { doit = > 0 , nohm = > 0 , level = > 'L14' , func = > '_getBalanceTotalData' } ,
2020-06-17 09:16:57 +00:00
) ;
2020-08-06 19:39:34 +00:00
2020-11-17 16:48:00 +00:00
my % hua = ( # mögliche Random UserAgents
2020-11-11 16:34:09 +00:00
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" ,
3 = > "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36" ,
2020-11-16 20:38:10 +00:00
4 = > "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36 Edg/86.0.622.38" ,
5 = > "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0" ,
6 = > "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4044.129 Safari/537.36" ,
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" ,
9 = > "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0" ,
2020-11-17 16:48:00 +00:00
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" ,
) ;
2020-12-01 20:51:52 +00:00
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 = SMA Schaltdosen
2020-12-02 09:37:43 +00:00
315 = > { arg = > ":slider,0,1,100" , fn = > \ & _manageConsumerByEnergy } , # 315 = Geräte über SEMP (z.B. SMA EV Charger)
2020-12-01 20:51:52 +00:00
366 = > { arg = > ":on,off,auto" , fn = > \ & _switchConsumer } , # 366 = Edimax sp-2101w v1 Schaltdosen
2020-12-02 09:37:43 +00:00
425 = > { arg = > ":on,off,auto" , fn = > \ & _switchConsumer } , # 425 = Fritz Dect Steckdosen
2020-12-01 20:51:52 +00:00
) ;
# Tags der verfügbaren Datenquellen
2020-07-02 19:45:08 +00:00
my @ pd = qw( plantMasterData
consumerMasterdata
balanceDayData
balanceMonthData
balanceYearData
balanceTotalData
consumerCurrentdata
2020-06-11 12:18:13 +00:00
consumerDayData
consumerMonthData
2020-06-20 08:02:04 +00:00
consumerYearData
forecastData
liveData
weatherData
plantLogbook
2020-06-11 12:18:13 +00:00
) ;
2020-04-20 14:55:36 +00:00
###############################################################
# SMAPortal Initialize
###############################################################
sub Initialize {
my ( $ hash ) = @ _ ;
2020-06-11 12:18:13 +00:00
2020-06-20 08:02:04 +00:00
my @ pls ;
for my $ p ( @ pd ) {
push @ pls , $ p if ( ! $ stpl { $ p } { doit } ) ;
}
my $ prov = join "," , @ pls ;
my $ v5d = join "," , @ pd ;
2020-04-20 14:55:36 +00:00
$ hash - > { DefFn } = \ & Define ;
$ hash - > { UndefFn } = \ & Undefine ;
$ hash - > { DeleteFn } = \ & Delete ;
$ hash - > { AttrFn } = \ & Attr ;
$ hash - > { SetFn } = \ & Set ;
$ hash - > { GetFn } = \ & Get ;
$ hash - > { DbLog_splitFn } = \ & DbLog_split ;
2020-08-06 19:39:34 +00:00
$ hash - > { AttrList } = "balanceDay " .
"balanceMonth " .
"balanceYear " .
"cookieLocation " .
2020-10-10 19:05:38 +00:00
"cookieDelete:auto,afterRun,afterCycle,afterAttempt,afterAttempt&Run " .
2020-04-20 14:55:36 +00:00
"disable:0,1 " .
"interval " .
2020-07-02 19:45:08 +00:00
"noHomeManager:1,0 " .
2020-06-20 08:02:04 +00:00
"plantLogbookTypes:multiple-strict,Info,Warning,Disturbance,Error " .
"plantLogbookApprovalState:Any,NotApproved " .
"providerLevel:multiple-strict," . $ prov . " " .
2020-04-20 14:55:36 +00:00
"showPassInLog:1,0 " .
"userAgent " .
2020-10-11 19:25:54 +00:00
"useRelativeNames:1,0 " .
2020-11-17 16:48:00 +00:00
"verbose5Data:multiple-strict,none,loginData,detailViewSwitch,switchConsumer," . $ v5d . " " .
2020-04-20 14:55:36 +00:00
$ readingFnAttributes ;
eval { FHEM::Meta:: InitMod ( __FILE__ , $ hash ) } ; ## no critic 'eval' # für Meta.pm (https://forum.fhem.de/index.php/topic,97589.0.html)
return ;
}
2019-03-16 06:48:59 +00:00
2019-03-03 20:08:04 +00:00
###############################################################
# SMAPortal Define
###############################################################
2020-04-20 14:55:36 +00:00
sub Define {
2019-03-03 20:08:04 +00:00
my ( $ hash , $ def ) = @ _ ;
2020-04-20 14:55:36 +00:00
my @ a = split ( /\s+/x , $ def ) ;
2019-03-03 20:08:04 +00:00
return "Wrong syntax: use \"define <name> SMAPortal\" " if ( int ( @ a ) < 1 ) ;
2019-03-25 19:19:05 +00:00
$ hash - > { HELPER } { MODMETAABSENT } = 1 if ( $ modMetaAbsent ) ; # Modul Meta.pm nicht vorhanden
2019-06-12 21:29:10 +00:00
$ hash - > { HELPER } { GETTER } = "all" ;
$ hash - > { HELPER } { SETTER } = "none" ;
2019-05-30 07:44:31 +00:00
setVersionInfo ( $ hash ) ; # Versionsinformationen setzen
getcredentials ( $ hash , 1 ) ; # Credentials lesen und in RAM laden ($boot=1)
2020-04-20 14:55:36 +00:00
CallInfo ( $ hash ) ; # Start Daten Abrufschleife
2019-03-03 20:08:04 +00:00
2020-04-20 14:55:36 +00:00
return ;
2019-03-03 20:08:04 +00:00
}
###############################################################
# SMAPortal Undefine
###############################################################
2020-04-20 14:55:36 +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 } ) ;
2020-04-20 14:55:36 +00:00
return ;
2019-03-03 20:08:04 +00:00
}
###############################################################
# SMAPortal Delete
###############################################################
2020-04-20 14:55:36 +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 ) ;
2020-04-20 14:55:36 +00:00
return ;
2019-03-03 20:08:04 +00:00
}
###############################################################
# SMAPortal Set
###############################################################
2020-11-04 16:55:41 +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 ) ;
2020-11-12 20:50:51 +00:00
my $ name = shift @ a ;
my $ opt = shift @ a ;
my $ arg = join " " , map { my $ p = $ _ ; $ p =~ s/\s//xg ; $ p ; } @ a ; ## no critic 'Map blocks'
my $ prop = shift @ a ;
my $ prop1 = shift @ a ;
2020-12-01 20:51:52 +00:00
my ( $ setlist , @ ads , $ susyid ) ;
2019-06-12 21:29:10 +00:00
my $ ad = "" ;
2019-03-03 20:08:04 +00:00
return if ( IsDisabled ( $ name ) ) ;
if ( ! $ hash - > { CREDENTIALS } ) {
# initiale setlist für neue Devices
$ setlist = "Unknown argument $opt, choose one of " .
2020-04-20 14:55:36 +00:00
"credentials "
2019-03-03 20:08:04 +00:00
;
2020-10-11 19:25:54 +00:00
}
else {
2019-03-03 20:08:04 +00:00
# erweiterte Setlist wenn Credentials gesetzt
$ setlist = "Unknown argument $opt, choose one of " .
2020-04-20 14:55:36 +00:00
"credentials " .
2020-08-08 12:22:12 +00:00
"createPortalGraphic:Generation,Consumption,Generation_Consumption,Differential " .
"getData:noArg "
2019-03-03 20:08:04 +00:00
;
2020-12-01 20:51:52 +00:00
2019-06-12 21:29:10 +00:00
if ( $ hash - > { HELPER } { PLANTOID } && $ hash - > { HELPER } { CONSUMER } ) {
2020-11-05 22:01:07 +00:00
for my $ key ( keys % { $ hash - > { HELPER } { CONSUMER } } ) {
2020-11-05 22:05:30 +00:00
my $ dev = $ hash - > { HELPER } { CONSUMER } { $ key } { DeviceName } ;
2019-06-12 21:29:10 +00:00
if ( $ dev ) {
2020-12-01 20:51:52 +00:00
$ susyid = $ hash - > { HELPER } { CONSUMER } { $ key } { SUSyID } ;
2020-11-05 19:48:33 +00:00
push @ ads , $ dev ;
2020-12-01 20:51:52 +00:00
$ setlist . = $ dev . $ hsusyid { $ susyid } { arg } . " " ;
2019-06-12 21:29:10 +00:00
}
}
2020-11-05 21:27:38 +00:00
}
2019-03-03 20:08:04 +00:00
}
2020-11-05 19:48:33 +00:00
if ( @ ads ) {
$ ad = join "|" , @ ads ;
}
2020-11-17 16:48:00 +00:00
2020-08-09 07:32:15 +00:00
if ( $ opt && $ ad && $ opt =~ /$ad/x ) {
2019-06-12 21:29:10 +00:00
# Verbraucher schalten
2020-12-01 20:51:52 +00:00
#$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 ;
}
}
2019-06-12 21:29:10 +00:00
$ hash - > { HELPER } { GETTER } = "none" ;
2020-12-01 20:51:52 +00:00
$ hash - > { HELPER } { SETTER } = "$opt:$susyid:$prop" ;
2020-10-11 19:25:54 +00:00
CallInfo ( $ hash ) ;
}
else {
2020-08-09 07:32:15 +00:00
my $ params = {
hash = > $ hash ,
name = > $ name ,
opt = > $ opt ,
prop = > $ prop ,
prop1 = > $ prop1 ,
aref = > \ @ a ,
} ;
2020-11-04 16:55:41 +00:00
if ( $ hset { $ opt } && defined & { $ hset { $ opt } { fn } } ) {
my $ ret = q{ } ;
$ ret = & { $ hset { $ opt } { fn } } ( $ params ) ;
2020-08-09 07:32:15 +00:00
return $ ret ;
}
2019-03-03 20:08:04 +00:00
return "$setlist" ;
2019-06-12 21:29:10 +00:00
}
2019-03-03 20:08:04 +00:00
return ;
}
2020-08-09 07:32:15 +00:00
################################################################
# Setter credentials
# credentials speichern
################################################################
sub _setCredentials { ## no critic "not used"
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ prop = $ paref - > { prop } ;
my $ prop1 = $ paref - > { prop1 } ;
return qq{ Credentials are incomplete, use "set $name credentials <username> <password>" } if ( ! $ prop || ! $ prop1 ) ;
my ( $ success ) = setcredentials ( $ hash , $ prop , $ prop1 ) ;
if ( $ success ) {
delcookiefile ( $ hash ) ;
CallInfo ( $ hash ) ;
return "Username and Password saved successfully" ;
2020-10-11 19:25:54 +00:00
}
else {
2020-08-09 07:32:15 +00:00
return "Error while saving Username / Password - see logfile for details" ;
}
return ;
}
################################################################
# Setter getData
# identisch zu "get gata", Workaround um mit webCmd
# arbeiten zu können
################################################################
sub _setGetData { ## no critic "not used"
my $ paref = shift ;
my $ name = $ paref - > { name } ;
CommandGet ( undef , "$name data" ) ;
return ;
}
################################################################
# Setter createPortalGraphic
# create createPortalGraphic devices
################################################################
sub _setCreatePortalGraphic { ## no critic "not used"
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ prop = $ paref - > { prop } ;
if ( ! $ hash - > { CREDENTIALS } ) { return "Credentials of $name are not set - make sure you've set it with \"set $name credentials username password\"" ; }
my ( $ htmldev , $ ret , $ c , $ type , $ color2 ) ;
if ( $ prop eq "Generation" ) { ## no critic "Cascading"
$ htmldev = "SPG1.$name" ; # Grafiktyp Generation (Erzeugung)
$ type = 'pv' ;
$ c = "SMA Sunny Portal Graphics - Forecast Generation" ;
$ color2 = "000000" ; # zweite Farbe als schwarz setzen
2020-10-11 19:25:54 +00:00
}
elsif ( $ prop eq "Consumption" ) {
2020-08-09 07:32:15 +00:00
$ htmldev = "SPG2.$name" ; # Grafiktyp Consumption (Verbrauch)
$ type = 'co' ;
$ c = "SMA Sunny Portal Graphics - Forecast Consumption" ;
$ color2 = "000000" ; # zweite Farbe als schwarz setzen
2020-10-11 19:25:54 +00:00
}
elsif ( $ prop eq "Generation_Consumption" ) {
2020-08-09 07:32:15 +00:00
$ htmldev = "SPG3.$name" ; # Grafiktyp Generation_Consumption (Erzeugung und Verbrauch)
$ type = 'pvco' ;
$ c = "SMA Sunny Portal Graphics - Forecast Generation & Consumption" ;
$ color2 = "FF5C82" ; # zweite Farbe als rot setzen
2020-10-11 19:25:54 +00:00
}
elsif ( $ prop eq "Differential" ) {
2020-08-09 07:32:15 +00:00
$ htmldev = "SPG4.$name" ; # Grafiktyp Differential (Differenzanzeige)
$ type = 'diff' ;
$ c = "SMA Sunny Portal Graphics - Forecast Differential" ;
$ color2 = "FF5C82" ; # zweite Farbe als rot setzen
2020-10-11 19:25:54 +00:00
}
else {
2020-08-09 07:32:15 +00:00
return "Invalid portal graphic devicetype ! Use one of \"Generation\", \"Consumption\", \"Generation_Consumption\", \"Differential\". "
}
$ ret = CommandDefine ( $ hash - > { CL } , "$htmldev SMAPortalSPG {FHEM::SMAPortal::PortalAsHtml ('$name','$htmldev')}" ) ;
return $ ret if ( $ ret ) ;
CommandAttr ( $ hash - > { CL } , "$htmldev alias $c" ) ; # Alias setzen
$ c = qq{ This device provides a praphical output of SMA Sunny Portal values. \ n } .
qq{ It is important that a SMA Home Manager is installed. Otherwise no forecast data are provided by SMA! \ n } .
qq{ The device "$name" needs to contain "forecastData" in attribute "providerLevel". \ n } .
qq{ The attribute "providerLevel" must also contain "consumerCurrentdata" if you want switch your consumer connectet to the SMA Home Manager. } ;
CommandAttr ( $ hash - > { CL } , "$htmldev comment $c" ) ;
# es muß nicht unbedingt jedes der möglichen userattr unbedingt vorbesetzt werden
# bzw muß überhaupt hier etwas vorbesetzt werden ?
# alle Werte enstprechen eh den AttrVal/ AttrNum default Werten
CommandAttr ( $ hash - > { CL } , "$htmldev hourCount 24" ) ;
CommandAttr ( $ hash - > { CL } , "$htmldev consumerAdviceIcon on" ) ;
CommandAttr ( $ hash - > { CL } , "$htmldev showHeader 1" ) ;
CommandAttr ( $ hash - > { CL } , "$htmldev showLink 1" ) ;
CommandAttr ( $ hash - > { CL } , "$htmldev spaceSize 24" ) ;
CommandAttr ( $ hash - > { CL } , "$htmldev showWeather 1" ) ;
CommandAttr ( $ hash - > { CL } , "$htmldev layoutType $type" ) ; # Anzeigetyp setzen
# eine mögliche Startfarbe steht beim installiertem f18 Style direkt zur Verfügung
# ohne vorhanden f18 Style bestimmt später tr.odd aus der Style css die Anfangsfarbe
my $ color ;
my $ jh = json2nameValue ( AttrVal ( 'WEB' , 'styleData' , '' ) ) ;
if ( $ jh && ref $ jh eq "HASH" ) {
$ color = $ jh - > { 'f18_cols.header' } ;
}
if ( defined ( $ color ) ) {
CommandAttr ( $ hash - > { CL } , "$htmldev beamColor $color" ) ;
}
# zweite Farbe setzen
CommandAttr ( $ hash - > { CL } , "$htmldev beamColor2 $color2" ) ;
my $ room = AttrVal ( $ name , "room" , "SMAPortal" ) ;
CommandAttr ( $ hash - > { CL } , "$htmldev room $room" ) ;
return "SMA Portal Graphics device \"$htmldev\" created and assigned to room \"$room\"." ;
}
2019-06-12 21:29:10 +00:00
###############################################################
# SMAPortal Get
###############################################################
2020-04-20 14:55:36 +00:00
sub Get {
2019-06-12 21:29:10 +00:00
my ( $ hash , @ a ) = @ _ ;
return "\"get X\" needs at least an argument" if ( @ a < 2 ) ;
my $ name = shift @ a ;
my $ opt = shift @ a ;
2020-04-20 14:55:36 +00:00
my $ getlist = "Unknown argument $opt, choose one of " .
"storedCredentials:noArg " .
"data:noArg " ;
2019-06-12 21:29:10 +00:00
return "module is disabled" if ( IsDisabled ( $ name ) ) ;
if ( $ opt eq "data" ) {
$ hash - > { HELPER } { GETTER } = "all" ;
$ hash - > { HELPER } { SETTER } = "none" ;
2020-06-20 08:02:04 +00:00
2019-06-12 21:29:10 +00:00
CallInfo ( $ hash ) ;
2020-10-11 19:25:54 +00:00
}
elsif ( $ opt eq "storedCredentials" ) {
2020-04-20 14:55:36 +00:00
if ( ! $ hash - > { CREDENTIALS } ) { return "Credentials of $name are not set - make sure you've set it with \"set $name credentials <username> <password>\"" ; }
2019-06-12 21:29:10 +00:00
# Credentials abrufen
my ( $ success , $ username , $ password ) = getcredentials ( $ hash , 0 ) ;
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" .
2020-10-11 19:25:54 +00:00
"\n" ;
}
else {
2019-06-12 21:29:10 +00:00
return "$getlist" ;
}
2020-04-20 14:55:36 +00:00
return ;
2019-06-12 21:29:10 +00:00
}
2019-03-21 21:39:30 +00:00
###############################################################
# SMAPortal DbLog_splitFn
###############################################################
2020-04-20 14:55:36 +00:00
sub DbLog_split {
2019-03-21 21:39:30 +00:00
my ( $ event , $ device ) = @ _ ;
my ( $ reading , $ value , $ unit ) ;
2020-08-09 12:40:04 +00:00
if ( $ event =~ /\s(Wh|W|kWh|%|h)$/xms ) {
( $ reading , $ value , $ unit ) = $ event =~ /(.*):\s(.*)\s(.*)/x ;
2019-03-22 14:46:36 +00:00
}
2019-03-21 21:39:30 +00:00
return ( $ reading , $ value , $ unit ) ;
}
2019-03-03 20:08:04 +00:00
######################################################################################
# Username / Paßwort speichern
######################################################################################
2020-04-20 14:55:36 +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 "" ,
2020-08-06 19:39:34 +00:00
map { $ i = ( $ i + 1 ) % $ len ; chr ( ( ord ( $ _ ) + $ key [ $ i ] ) % 256 ) } split // , $ credstr ; ## no critic 'Map blocks';
2019-03-03 20:08:04 +00:00
# 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 ;
2020-10-11 19:25:54 +00:00
}
else {
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
######################################################################################
2020-04-20 14:55:36 +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 ) ;
2020-04-21 12:34:12 +00:00
if ( $ boot ) { # mit $boot=1 Credentials von Platte lesen und als scrambled-String in RAM legen
2019-03-03 20:08:04 +00:00
$ 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 ;
}
2020-04-21 12:34:12 +00:00
if ( $ credstr ) { # beim Boot scrambled Credentials in den RAM laden
$ hash - > { HELPER } { ".CREDENTIALS" } = $ credstr ;
2019-03-03 20:08:04 +00:00
2020-04-21 12:34:12 +00:00
$ hash - > { CREDENTIALS } = "Set" ; # "Credentials" wird als Statusbit ausgewertet. Wenn nicht gesetzt -> Warnmeldung und keine weitere Verarbeitung
2019-03-03 20:08:04 +00:00
$ success = 1 ;
}
2020-10-11 19:25:54 +00:00
}
else { # boot = 0 -> Credentials aus RAM lesen, decoden und zurückgeben
2020-04-21 12:34:12 +00:00
$ credstr = $ hash - > { HELPER } { ".CREDENTIALS" } // $ hash - > { HELPER } { CREDENTIALS } ; # Kompatibilität zu Versionen vor 2.6.1
2019-03-03 20:08:04 +00:00
if ( $ credstr ) {
# Beginn Descramble-Routine
@ key = qw( 1 3 4 5 6 3 2 1 9 ) ;
$ len = scalar @ key ;
$ i = 0 ;
$ credstr = join "" ,
2020-08-06 19:39:34 +00:00
map { $ i = ( $ i + 1 ) % $ len ; chr ( ( ord ( $ _ ) - $ key [ $ i ] + 256 ) % 256 ) } split // , $ credstr ; ## no critic 'Map blocks';
2019-03-03 20:08:04 +00:00
# Ende Descramble-Routine
( $ username , $ passwd ) = split ( ":" , decode_base64 ( $ credstr ) ) ;
2020-06-20 11:37:04 +00:00
my $ logpw = AttrVal ( $ name , "showPassInLog" , 0 ) ? $ passwd : "********" ;
2019-03-03 20:08:04 +00:00
2019-06-13 19:20:53 +00:00
Log3 ( $ name , 4 , "$name - Credentials read from RAM: $username $logpw" ) ;
2020-10-11 19:25:54 +00:00
}
else {
2019-03-03 20:08:04 +00:00
Log3 ( $ name , 1 , "$name - Credentials not set in RAM !" ) ;
}
2019-05-30 07:44:31 +00:00
2019-03-03 20:08:04 +00:00
$ success = ( defined ( $ passwd ) ) ? 1 : 0 ;
}
return ( $ success , $ username , $ passwd ) ;
}
###############################################################
# SMAPortal Attr
###############################################################
2020-04-20 14:55:36 +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" ) ;
2020-04-20 14:55:36 +00:00
$ val = ( $ do == 1 ? "disabled" : "initialized" ) ;
2019-03-03 20:08:04 +00:00
if ( $ do ) {
2020-06-26 11:11:23 +00:00
deleteData ( $ hash ) ;
delcookiefile ( $ hash ) ;
2019-03-03 20:08:04 +00:00
delete $ hash - > { MODE } ;
2020-05-30 20:51:54 +00:00
RemoveInternalTimer ( $ hash ) ;
2020-10-11 19:25:54 +00:00
}
else {
2020-05-30 20:51:54 +00:00
InternalTimer ( gettimeofday ( ) + 1.0 , "FHEM::SMAPortal::CallInfo" , $ hash , 0 ) ;
2019-03-03 20:08:04 +00:00
}
2020-04-20 14:55:36 +00:00
2019-03-03 20:08:04 +00:00
readingsBeginUpdate ( $ hash ) ;
2020-06-20 17:24:31 +00:00
readingsBulkUpdate ( $ hash , "state" , $ val ) ;
readingsBulkUpdate ( $ hash , "loginState" , "unknown" ) ;
2020-04-20 14:55:36 +00:00
readingsEndUpdate ( $ hash , 1 ) ;
2019-06-03 23:00:53 +00:00
InternalTimer ( gettimeofday ( ) + 2.0 , "FHEM::SMAPortal::SPGRefresh" , "$name,0,1" , 0 ) ;
2019-03-03 20:08:04 +00:00
}
if ( $ cmd eq "set" ) {
2020-08-06 19:39:34 +00:00
if ( $ aName eq "interval" ) {
unless ( $ aVal =~ /^\d+$/x ) { return "The value for $aName is not valid. Use only figures 0-9 !" ; }
2020-11-11 16:34:09 +00:00
return qq{ The interval must be >= 180 seconds or 0 if you don't want use automatic updates } if ( $ aVal > 0 && $ aVal < 180 ) ;
2019-03-16 06:48:59 +00:00
InternalTimer ( gettimeofday ( ) + 1.0 , "FHEM::SMAPortal::CallInfo" , $ hash , 0 ) ;
2020-08-08 10:29:35 +00:00
}
2019-03-03 20:08:04 +00:00
}
2020-04-20 14:55:36 +00:00
return ;
2019-03-03 20:08:04 +00:00
}
################################################################
2020-05-30 20:51:54 +00:00
# Hauptschleife BlockingCall
# $hash->{HELPER}{GETTER} -> Flag für get Informationen
# $hash->{HELPER}{SETTER} -> Parameter für set-Befehl
2020-09-30 18:35:10 +00:00
# $nc = 1 weiterer Cycle (Cycle Zähler nicht zurücksetzten)
# $nr = 1 weiterer Retry (Zähler nicht zurücksetzten)
2019-03-03 20:08:04 +00:00
################################################################
2020-08-06 19:39:34 +00:00
sub CallInfo { ## no critic 'complexity'
2020-05-31 08:53:05 +00:00
my ( $ hash , $ nc , $ nr ) = @ _ ;
my $ name = $ hash - > { NAME } ;
2019-03-03 20:08:04 +00:00
my $ new ;
2019-03-16 06:48:59 +00:00
RemoveInternalTimer ( $ hash , "FHEM::SMAPortal::CallInfo" ) ;
2019-03-03 20:08:04 +00:00
2020-07-06 21:31:24 +00:00
my ( $ interval , $ maxcycles , $ timeout ) = controlParams ( $ name ) ;
2020-05-31 16:03:46 +00:00
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" ;
2020-10-11 19:25:54 +00:00
}
else {
2019-03-03 20:08:04 +00:00
$ new = gettimeofday ( ) + $ interval ;
2020-05-30 20:51:54 +00:00
InternalTimer ( $ new , "FHEM::SMAPortal::CallInfo" , $ hash , 0 ) ; # Wiederholungsintervall
2019-03-03 20:08:04 +00:00
$ hash - > { MODE } = "Automatic - next polltime: " . FmtTime ( $ new ) ;
}
return if ( IsDisabled ( $ name ) ) ;
2020-06-20 13:52:14 +00:00
for my $ key ( keys % stpl ) { # festlegen welche Daten geliefert werden sollen
2020-07-02 19:45:08 +00:00
if ( $ stpl { $ key } { doit } ) { # Datenprovider nach Login ausführen (mandatories)
$ mandatory { $ name } { $ key } { doit } = $ stpl { $ key } { doit } ;
$ mandatory { $ name } { $ key } { level } = $ stpl { $ key } { level } ;
$ mandatory { $ name } { $ key } { func } = $ stpl { $ key } { func } ;
next ;
}
2020-06-20 08:02:04 +00:00
$ subs { $ name } { $ key } { doit } = $ stpl { $ key } { doit } ;
$ subs { $ name } { $ key } { level } = $ stpl { $ key } { level } ;
$ subs { $ name } { $ key } { func } = $ stpl { $ key } { func } ;
}
2020-07-02 19:45:08 +00:00
2020-06-20 08:02:04 +00:00
my @ pl = split "," , AttrVal ( $ name , "providerLevel" , "" ) ;
for my $ p ( @ pl ) {
$ subs { $ name } { $ p } { doit } = 1 ;
}
2019-03-03 20:08:04 +00:00
if ( $ hash - > { HELPER } { RUNNING_PID } ) {
2020-12-01 20:51:52 +00:00
if ( $ hash - > { HELPER } { RUNNING_PID } { pid } =~ m/DEAD/ ) { # tote PID's löschen
delete $ hash - > { HELPER } { RUNNING_PID } ;
}
else {
Log3 ( $ name , 3 , "$name - An old data cycle is still running, the new data cycle start is postponed." ) ;
return ;
}
2019-03-03 20:08:04 +00:00
}
2019-03-09 08:32:50 +00:00
2020-04-20 14:55:36 +00:00
my $ getp = $ hash - > { HELPER } { GETTER } ;
my $ setp = $ hash - > { HELPER } { SETTER } ;
2020-05-31 08:53:05 +00:00
if ( ! $ nc && ! $ nr ) {
2020-12-01 20:51:52 +00:00
Log3 ( $ name , 4 , "$name - ################################################################" ) ;
Log3 ( $ name , 4 , "$name - ### start new set/get data from SMA Sunny Portal ###" ) ;
Log3 ( $ name , 4 , "$name - ################################################################" ) ;
2020-08-09 18:09:42 +00:00
Log3 ( $ name , 5 , "$name - SMAPortal version: $hash->{HELPER}{VERSION}" ) ;
Log3 ( $ name , 4 , "$name - calculated maximum cycles: $maxcycles" ) ;
Log3 ( $ name , 4 , "$name - calculated timeout: $timeout" ) ;
2020-05-31 08:53:05 +00:00
}
2019-06-12 21:29:10 +00:00
2020-07-02 19:45:08 +00:00
if ( AttrVal ( $ name , "noHomeManager" , 0 ) ) { # wenn kein Home Manager installiert ist keine mandatories ausführen
% mandatory = ( ) ;
for my $ k ( keys % stpl ) {
if ( $ stpl { $ k } { nohm } ) {
$ subs { $ name } { $ k } { doit } = 0 ;
Log3 ( $ name , 3 , qq{ $name - ignore provider "$k" - SMA Home Manager is not installed } ) if ( ! $ nc && ! $ nr ) ;
}
}
}
2020-09-30 18:35:10 +00:00
if ( ! $ nc ) { # kein weiterer Cycle, d.h. erster Cycle
2020-05-31 22:12:59 +00:00
$ hash - > { HELPER } { ACTCYCLE } = 1 ;
$ hash - > { HELPER } { CYCLEBTIME } = ( gettimeofday ( ) ) [ 0 ] ;
}
2020-09-30 18:35:10 +00:00
else { # es ist ein weiterer Cycle
if ( AttrVal ( $ name , "cookieDelete" , "auto" ) eq "afterCycle" ) {
delcookiefile ( $ hash ) ;
}
}
if ( ! $ nr ) { # es ist keine weiterer Attempt, d.h. erster Attempt
$ hash - > { HELPER } { RETRIES } = 1 ;
}
2020-05-31 08:53:05 +00:00
2020-06-20 08:02:04 +00:00
my $ ac = $ hash - > { HELPER } { ACTCYCLE } ;
2020-05-30 20:51:54 +00:00
2020-05-31 16:03:46 +00:00
Log3 ( $ name , 3 , "$name - Running data cycle: $ac of $maxcycles" ) ;
2020-05-30 20:51:54 +00:00
2020-05-31 16:03:46 +00:00
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdateIfChanged ( $ hash , "state" , "running - call cycle $ac" ) ;
readingsEndUpdate ( $ hash , 1 ) ;
2020-05-30 20:51:54 +00:00
2020-05-26 15:31:05 +00:00
$ hash - > { HELPER } { RUNNING_PID } = BlockingCall ( "FHEM::SMAPortal::GetSetData" , "$name|$getp|$setp" , "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 ;
}
2020-05-31 16:03:46 +00:00
################################################################
# Steuerparameter berechnen / festlegen
################################################################
sub controlParams {
my $ name = shift ;
# Voreinstellungen
2020-07-06 21:31:24 +00:00
my $ timeoutdef = 3600 ; # Standard Timeout
2020-05-31 16:03:46 +00:00
my $ definterval = 300 ; # Standard Interval
2020-05-31 22:12:59 +00:00
my $ buffer = 5 ; # Sicherheitspuffer zum nächsten Intervall
2020-05-31 16:03:46 +00:00
2020-05-31 22:12:59 +00:00
my $ interval = AttrVal ( $ name , "interval" , $ definterval ) ; # 0 wenn manuell gesteuert
2020-05-31 16:03:46 +00:00
my $ maxcycles = $ defmaxcycles ;
2020-07-06 21:31:24 +00:00
return ( $ interval , $ maxcycles , $ timeoutdef ) ;
2020-05-31 16:03:46 +00:00
}
2019-03-03 20:08:04 +00:00
################################################################
## Datenabruf SMA-Portal
2019-06-12 21:29:10 +00:00
## schaltet auch Verbraucher des Sunny Home Managers
2019-03-03 20:08:04 +00:00
################################################################
2020-08-06 19:39:34 +00:00
sub GetSetData { ## no critic 'complexity'
2020-12-01 20:51:52 +00:00
my ( $ string ) = @ _ ;
2020-06-26 11:11:23 +00:00
my ( $ name , $ getp , $ setp ) = split ( "\\|" , $ string ) ;
my $ hash = $ defs { $ name } ;
my $ cookieLocation = AttrVal ( $ name , "cookieLocation" , "./log/" . $ name . "_cookie.txt" ) ;
my $ v5d = AttrVal ( $ name , "verbose5Data" , "none" ) ;
my $ verbose = AttrVal ( $ name , "verbose" , 3 ) ;
my $ lang = AttrVal ( "global" , "language" , "EN" ) ;
my $ state = "ok" ;
my ( $ st , $ lc ) = ( "" , "" ) ;
my @ da = ( ) ;
2020-12-01 20:51:52 +00:00
my $ params ;
2019-06-12 21:29:10 +00:00
2020-06-11 12:18:13 +00:00
my ( $ errstate , $ reread , $ retry , $ exceed , $ newcycle ) = ( 0 , 0 , 0 , 0 , 0 ) ;
2020-06-20 11:18:13 +00:00
2020-11-11 16:34:09 +00:00
my @ ak = keys % hua ; # UserAgent zufällig vorbelegen
my $ randomua = $ ak [ rand @ ak ] ;
my $ defuseragent = $ hua { $ randomua } ;
my $ useragent = AttrVal ( $ name , "userAgent" , $ defuseragent ) ;
2019-06-10 16:32:58 +00:00
2020-12-01 20:51:52 +00:00
BlockingInformParent ( "FHEM::SMAPortal::setFromBlocking" , [ $ name , "usedUserAgent:$useragent" , "NULL" ] , 1 ) ;
2020-12-01 17:12:42 +00:00
2019-06-12 21:29:10 +00:00
Log3 ( $ name , 5 , "$name - Start operation with CookieLocation: $cookieLocation and UserAgent: $useragent" ) ;
2020-12-01 20:51:52 +00:00
Log3 ( $ name , 5 , "$name - data get: $getp, data set: $setp" ) ;
2019-03-03 20:08:04 +00:00
my $ ua = LWP::UserAgent - > new ;
2020-05-27 16:58:01 +00:00
2020-06-03 13:33:11 +00:00
# Default Header Daten
2020-05-27 16:58:01 +00:00
$ ua - > default_header ( "Accept" = > "*/*" ,
"Accept-Encoding" = > "gzip, deflate, br" ,
2020-06-20 11:18:13 +00:00
"Accept-Language" = > $ hal { $ lang } , # deutsch: de,en-US;q=0.7,en;q=0.3 , englisch: en-US;q=0.7,en;q=0.3
2020-05-27 16:58:01 +00:00
"Connection" = > "keep-alive" ,
"Cookie" = > "collapseNavi_state=shown" ,
"DNT" = > 1 ,
"Host" = > "www.sunnyportal.com" ,
"Referer" = > "https://www.sunnyportal.com/FixedPages/HoManLive.aspx" ,
"User-Agent" = > $ useragent ,
"X-Requested-With" = > "XMLHttpRequest"
) ;
2019-03-03 20:08:04 +00:00
# Cookies
$ ua - > cookie_jar ( HTTP::Cookies - > new ( file = > "$cookieLocation" ,
ignore_discard = > 1 ,
autosave = > 1
)
) ;
2020-05-28 06:52:47 +00:00
handleCounter ( $ name , "dailyCallCounter" ) ; # Abfragezähler setzen (Anzahl tägliche Wiederholungen von GetSetData)
2019-06-12 21:29:10 +00:00
2020-06-03 13:33:11 +00:00
### Login
##############
2020-07-02 19:45:08 +00:00
my $ paref = [ $ name , $ ua , $ state , $ errstate ] ;
2020-06-26 11:11:23 +00:00
( $ state , $ errstate ) = _doLogin ( $ paref ) ;
2019-03-03 20:08:04 +00:00
2020-06-09 10:40:42 +00:00
if ( $ errstate ) {
2020-06-03 13:33:11 +00:00
$ st = encode_base64 ( $ state , "" ) ;
2020-06-09 10:40:42 +00:00
return "$name|0|0|$errstate|$getp|$setp|$st" ;
2020-11-03 21:13:03 +00:00
}
2020-06-20 11:18:13 +00:00
2020-07-02 19:45:08 +00:00
### die Anlagen Asset Daten auslesen (Funktionen aus %mandatory mit doit=1)
### (Hash %mandatory ist leer wenn kein SMA Home Manager eingesetzt)
2020-06-20 11:18:13 +00:00
##################################################################################
2020-07-02 19:45:08 +00:00
for my $ k ( keys % { $ mandatory { $ name } } ) {
next if ( ! $ mandatory { $ name } { $ k } { doit } ) ;
no strict "refs" ; ## no critic 'NoStrict'
( $ errstate , $ state , $ reread , $ retry ) = & { $ mandatory { $ name } { $ k } { func } } ( { name = > $ name ,
ua = > $ ua ,
state = > $ state ,
daref = > \ @ da
} ) ;
2020-06-20 11:18:13 +00:00
use strict "refs" ;
if ( $ errstate ) {
$ st = encode_base64 ( $ state , "" ) ;
return "$name|0|0|$errstate|$getp|$setp|$st" ;
}
2020-05-31 08:53:05 +00:00
}
2020-06-03 13:33:11 +00:00
2020-12-01 20:51:52 +00:00
### Verbraucher schalten / managen
2020-05-30 20:51:54 +00:00
#######################################
2020-12-01 20:51:52 +00:00
if ( $ setp ne "none" ) {
my ( $ d , $ susyid , $ op ) = split ( ":" , $ setp ) ; # $op -> Verbraucher Manage Operation
2020-12-01 17:12:42 +00:00
2020-12-01 20:51:52 +00:00
$ params = {
name = > $ name ,
ua = > $ ua ,
state = > $ state ,
daref = > \ @ da ,
d = > $ d ,
susyid = > $ susyid ,
op = > $ op
} ;
2020-12-01 17:12:42 +00:00
2020-12-01 20:51:52 +00:00
if ( $ hsusyid { $ susyid } && defined & { $ hsusyid { $ susyid } { fn } } ) {
( $ errstate , $ state ) = & { $ hsusyid { $ susyid } { fn } } ( $ params ) ;
}
2020-10-11 19:25:54 +00:00
else {
2020-12-01 20:51:52 +00:00
$ 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" ) ;
2020-05-30 20:51:54 +00:00
}
}
### Daten abrufen
2020-06-03 19:30:51 +00:00
#############################
2020-11-03 19:47:39 +00:00
if ( $ getp ne "none" ) {
2020-06-20 08:02:04 +00:00
2020-11-04 16:55:41 +00:00
_detailViewOn ( { name = > $ name ,
ua = > $ ua ,
state = > $ state ,
daref = > \ @ da
} ) ; # Detailanzeige einschalten
2020-06-20 08:02:04 +00:00
for my $ k ( keys % { $ subs { $ name } } ) {
2020-06-27 10:35:48 +00:00
next if ( ! $ subs { $ name } { $ k } { doit } ) ;
2020-11-04 16:55:41 +00:00
2020-06-27 10:35:48 +00:00
no strict "refs" ; ## no critic 'NoStrict'
2020-11-04 16:55:41 +00:00
2020-06-27 10:35:48 +00:00
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 } ) ;
2020-06-27 10:22:57 +00:00
next ;
}
2020-11-04 16:55:41 +00:00
2020-06-20 08:02:04 +00:00
( $ errstate , $ state , $ reread , $ retry ) = & { $ subs { $ name } { $ k } { func } } ( { name = > $ name ,
ua = > $ ua ,
state = > $ state ,
daref = > \ @ da
} ) ;
use strict "refs" ;
2020-06-03 19:30:51 +00:00
2020-06-09 10:40:42 +00:00
if ( $ errstate ) {
2020-06-09 07:18:56 +00:00
$ st = encode_base64 ( $ state , "" ) ;
2020-06-09 10:40:42 +00:00
return "$name|0|0|$errstate|$getp|$setp|$st" ;
2020-06-11 12:18:13 +00:00
}
2020-06-09 13:37:45 +00:00
2020-06-20 08:02:04 +00:00
goto & GetSetData if ( $ reread ) ;
2020-06-09 07:18:56 +00:00
2020-06-20 08:02:04 +00:00
# Wiederholung Datenabruf innerhalb eines Cycle
2020-06-26 11:11:23 +00:00
my $ retc = $ hash - > { HELPER } { RETRIES } ; # aktuelle Retry-Zähler
2020-06-20 08:02:04 +00:00
if ( $ retry && $ retc < $ maxretries ) { # neuer Retry im gleichen Zyklus (nicht wenn Verbraucher schalten)
$ hash - > { HELPER } { RETRIES } + + ;
2020-09-30 18:35:10 +00:00
my $ cd = AttrVal ( $ name , "cookieDelete" , "auto" ) ;
2020-11-17 16:48:00 +00:00
if ( $ retc == $ thold || $ cd =~ /Attempt/x ) { # Schwellenwert Leseversuche erreicht -> Cookie File löschen
2020-09-30 18:35:10 +00:00
my $ msg = qq{ $name - Threshold reached, delete cookie file before retry... } ;
2020-11-17 16:48:00 +00:00
if ( $ cd =~ /Attempt/x ) {
$ msg = qq{ $name - force delete cookie file before retry... } ;
}
Log3 ( $ name , 3 , $ msg ) ;
2020-10-10 19:05:38 +00:00
sleep $ sleepretry ; # Threshold exceed -> Retry mit Cookie löschen
2020-06-20 08:02:04 +00:00
$ exceed = 1 ;
BlockingInformParent ( "FHEM::SMAPortal::setFromBlocking" , [ $ name , "NULL" , "RETRIES:" . $ hash - > { HELPER } { RETRIES } ] , 1 ) ;
return "$name|$exceed|$newcycle|$errstate|$getp|$setp" ;
2020-06-09 13:37:45 +00:00
}
2020-05-30 20:51:54 +00:00
2020-11-17 16:48:00 +00:00
sleep $ sleepretry ;
2020-06-20 08:02:04 +00:00
goto & GetSetData ;
}
# Wiederholung Datenabruf in einem neuen Cycle
my $ ac = $ hash - > { HELPER } { ACTCYCLE } ;
my $ maxcycles = ( controlParams $ name ) [ 1 ] ;
if ( $ retry && $ ac < $ maxcycles ) { # neuer Zyklus (nicht wenn Verbraucher schalten)
2020-07-03 07:17:50 +00:00
Log3 ( $ name , 3 , qq{ $name - Maximum retries reached, start new data get cycle in $sleepexc seconds ... } ) ;
2020-06-20 08:02:04 +00:00
$ newcycle = 1 ;
return "$name|$exceed|$newcycle|$errstate|$getp|$setp" ;
}
2020-05-30 20:51:54 +00:00
}
2020-09-30 18:35:10 +00:00
if ( ! @ da ) {
$ state = "Warning - empty data received, values not current" ;
if ( AttrVal ( "global" , "language" , "EN" ) eq "DE" ) {
$ state = "Warnung - leere Daten empfangen, Werte nicht aktuell" ;
}
}
2020-05-30 20:51:54 +00:00
}
2019-06-12 21:29:10 +00:00
2019-03-03 20:08:04 +00:00
# Daten müssen als Einzeiler zurückgegeben werden
2020-12-01 20:51:52 +00:00
$ st = encode_base64 ( $ state , "" ) ;
2020-06-09 07:18:56 +00:00
if ( @ da ) {
$ lc = join "###" , @ da ;
2020-09-30 18:35:10 +00:00
$ lc = encode_base64 ( $ lc , "" ) ;
2020-12-01 20:51:52 +00:00
Log3 ( $ name , 3 , "$name - data retrieval done." ) ;
2020-06-09 07:18:56 +00:00
}
2020-06-09 10:40:42 +00:00
return "$name|$exceed|$newcycle|$errstate|$getp|$setp|$st|$lc" ;
2020-06-09 07:18:56 +00:00
}
################################################################
# Login Status checken und ggf. einloggen
################################################################
2020-06-26 11:11:23 +00:00
sub _doLogin {
2020-06-09 07:18:56 +00:00
my $ paref = shift ;
my $ name = $ paref - > [ 0 ] ;
my $ ua = $ paref - > [ 1 ] ;
my $ state = $ paref - > [ 2 ] ;
2020-06-09 10:40:42 +00:00
my $ errstate = $ paref - > [ 3 ] ;
2020-06-09 07:18:56 +00:00
my $ hash = $ defs { $ name } ;
my $ v5d = AttrVal ( $ name , "verbose5Data" , "none" ) ;
2020-06-11 21:35:57 +00:00
my $ verbose = AttrVal ( $ name , "verbose" , 3 ) ;
2020-06-09 07:18:56 +00:00
2020-11-17 16:48:00 +00:00
if ( $ verbose == 5 && $ v5d =~ /loginData/x ) {
2020-06-11 21:35:57 +00:00
$ ua - > add_handler ( request_send = > sub { shift - > dump ; return } ) ; # for debugging
$ ua - > add_handler ( response_done = > sub { shift - > dump ; return } ) ;
}
2020-06-26 11:11:23 +00:00
my ( $ success , $ username , $ password ) = getcredentials ( $ hash , 0 ) ; # gespeicherte Credentials abrufen
my $ loginp = $ ua - > post ( 'https://www.sunnyportal.com/Templates/Start.aspx' ) ;
my $ retcode = $ loginp - > code ;
my $ location = $ loginp - > header ( 'Location' ) // "" ;
my $ cookie = $ loginp - > header ( 'Set-Cookie' ) // "" ;
2020-06-09 07:18:56 +00:00
if ( $ loginp - > is_success ) {
2020-11-17 16:48:00 +00:00
if ( $ v5d =~ /loginData/x ) {
2020-06-09 07:18:56 +00:00
Log3 ( $ name , 5 , "$name - Status Login Page: " . $ loginp - > status_line ) ;
2020-06-26 11:11:23 +00:00
Log3 ( $ name , 5 , "$name - Header Location: " . $ location ) ;
Log3 ( $ name , 5 , "$name - Header Set-Cookie: " . $ cookie ) ;
2020-06-09 07:18:56 +00:00
}
$ retcode = $ loginp - > code ;
$ location = $ loginp - > header ( 'Location' ) // "" ;
2020-06-26 11:42:18 +00:00
if ( ! __isLoggedIn ( $ name , $ username , $ loginp ) ) { # keine aktive Session -> neuer Login
2020-06-09 07:18:56 +00:00
Log3 ( $ name , 4 , "$name - User not logged in. Try login with credentials ..." ) ;
if ( ! $ success ) {
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" ;
2020-06-09 10:40:42 +00:00
$ errstate = 1 ;
2020-10-11 19:25:54 +00:00
}
else {
2020-06-09 07:18:56 +00:00
my $ usernameField = "ctl00\$ContentPlaceHolder1\$Logincontrol1\$txtUserName" ;
my $ passwordField = "ctl00\$ContentPlaceHolder1\$Logincontrol1\$txtPassword" ;
my $ mempasswd = "ctl00\$ContentPlaceHolder1\$Logincontrol1\$MemorizePassword" ;
my $ loginField = "__EVENTTARGET" ;
my $ loginButton = "ctl00\$ContentPlaceHolder1\$Logincontrol1\$LoginBtn" ;
$ loginp = $ ua - > post ( 'https://www.sunnyportal.com/Templates/Start.aspx' , [ $ usernameField = > $ username , $ passwordField = > $ password , $ mempasswd = > "on" , "__EVENTTARGET" = > $ loginButton ] ) ;
$ retcode = $ loginp - > code ;
$ location = $ loginp - > header ( 'Location' ) // "" ;
2020-06-11 21:35:57 +00:00
if ( $ v5d =~ /loginData/ ) {
2020-06-09 07:18:56 +00:00
Log3 ( $ name , 5 , "$name - Status Redirect Page : " . $ retcode ) ;
Log3 ( $ name , 5 , "$name - Header Redirect Location: " . $ location ) ;
}
2020-06-17 09:16:57 +00:00
my $ sc = $ loginp - > header ( 'Set-Cookie' ) // "" ;
my ( $ logname ) = $ sc =~ /SunnyPortalLoginInfo=Username=(.*?)&/sx ;
Log3 ( $ name , 5 , "$name - Header Set-Cookie: " . $ sc ) if ( $ v5d =~ /loginData/ ) ;
2020-06-09 07:18:56 +00:00
2020-06-26 13:16:53 +00:00
if ( __isLoggedIn ( $ name , $ username , $ loginp ) ) { # Login erfolgeich(Landing Pages können im Portal eingestellt werden!)
2020-06-09 07:18:56 +00:00
handleCounter ( $ name , "dailyIssueCookieCounter" ) ; # Cookie Ausstellungszähler setzen
2020-06-20 08:02:04 +00:00
BlockingInformParent ( "FHEM::SMAPortal::setFromBlocking" , [ $ name , "loginState:successful" , "oldlogintime:" . ( gettimeofday ( ) ) [ 0 ] ] , 1 ) ;
2020-10-11 19:25:54 +00:00
$ errstate = 0 ;
}
else {
2020-06-09 07:18:56 +00:00
Log3 ( $ name , 2 , "$name - ERROR - Login into SMA-Portal failed !" ) ;
2020-06-27 10:15:45 +00:00
$ state = "login failed" ;
2020-06-20 17:54:40 +00:00
BlockingInformParent ( "FHEM::SMAPortal::setFromBlocking" , [ $ name , "loginState:failed" , "NULL" ] , 1 ) ;
2020-06-09 10:40:42 +00:00
$ errstate = 1 ;
2020-06-17 09:16:57 +00:00
}
2020-06-09 07:18:56 +00:00
}
}
2020-10-11 19:25:54 +00:00
}
elsif ( $ loginp - > is_redirect ) {
2020-06-09 07:18:56 +00:00
$ retcode = $ loginp - > code ;
$ location = $ loginp - > header ( 'Location' ) // "" ;
2020-06-26 11:42:18 +00:00
Log3 ( $ name , 3 , "$name - User is already logged in." ) ;
2020-06-11 21:35:57 +00:00
if ( $ v5d =~ /loginData/ ) {
2020-06-26 11:42:18 +00:00
Log3 ( $ name , 5 , "$name - Redirect return code: " . $ retcode ) ;
2020-06-09 07:18:56 +00:00
Log3 ( $ name , 5 , "$name - Redirect Header Location: " . $ location ) ;
}
2020-06-26 11:42:18 +00:00
2020-06-26 11:11:23 +00:00
BlockingInformParent ( "FHEM::SMAPortal::setFromBlocking" , [ $ name , "loginState:successful" , "NULL" ] , 1 ) ;
2020-06-09 10:40:42 +00:00
$ errstate = 0 ;
2020-10-11 19:25:54 +00:00
}
else {
2020-06-09 10:40:42 +00:00
$ errstate = 1 ;
2020-06-09 07:18:56 +00:00
$ state = $ loginp - > status_line ;
2020-06-20 17:54:40 +00:00
BlockingInformParent ( "FHEM::SMAPortal::setFromBlocking" , [ $ name , "loginState:failed" , "NULL" ] , 1 ) ;
2020-06-09 07:18:56 +00:00
Log3 ( $ name , 1 , "$name - ERROR Login Page: " . $ state ) ;
}
2020-06-12 06:54:53 +00:00
$ ua - > remove_handler ( 'request_send' ) ;
$ ua - > remove_handler ( 'response_done' ) ;
2020-06-11 21:35:57 +00:00
2020-06-09 10:40:42 +00:00
return ( $ state , $ errstate ) ;
2020-06-09 07:18:56 +00:00
}
2020-06-26 11:11:23 +00:00
################################################################
# Login Status testen
################################################################
sub __isLoggedIn {
2020-06-26 11:42:18 +00:00
my $ name = shift ;
2020-06-26 11:11:23 +00:00
my $ username = shift ;
2020-06-26 13:16:53 +00:00
my $ loginp = shift ;
2020-06-26 11:11:23 +00:00
my $ sc = $ loginp - > header ( 'Set-Cookie' ) // "" ;
my ( $ logname ) = $ sc =~ /SunnyPortalLoginInfo=Username=(.*?)&/sx ;
2020-06-26 11:42:18 +00:00
if ( $ logname && $ logname eq $ username ) {
Log3 ( $ name , 3 , "$name - Login into SMA-Portal successfully done with user: $logname" ) ;
2020-06-26 11:11:23 +00:00
return 1 ;
}
2020-06-26 13:16:53 +00:00
return 0 ;
2020-06-26 11:11:23 +00:00
}
2020-12-01 20:51:52 +00:00
################################################################
# 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 $ gcval = $ op / 100 ;
my $ pvval = 1 - $ gcval ;
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 $ pvlog = 100 - $ op ;
Log3 ( $ name , 4 , qq{ $name - Manage consumer "$d" (SuSyID $susyid): switch on if condition GridConsumption=$op% (PV=$pvlog%) 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( extractManageConsumerByEnergy ) ] ,
fields = > \ % fields ,
content = > $ cont ,
addon = > "$d:$susyid:$op" , # optionales Addon für aufzurufende Funktion
daref = > $ daref
} ) ;
return ( $ errstate , $ state ) ;
}
2020-06-17 09:16:57 +00:00
################################################################
# Abruf Live Daten
################################################################
2020-06-20 08:02:04 +00:00
sub _getLiveData { ## no critic "not used"
2020-06-26 11:11:23 +00:00
my $ paref = shift ;
my $ name = $ paref - > { name } ;
2020-09-29 20:56:15 +00:00
my $ ua = $ paref - > { ua } ; # LWP Useragent
2020-06-26 11:11:23 +00:00
my $ state = $ paref - > { state } ;
2020-09-29 20:56:15 +00:00
my $ daref = $ paref - > { daref } ; # Referenz zum Datenarray
2020-06-17 09:16:57 +00:00
my ( $ reread , $ retry , $ errstate ) = ( 0 , 0 , 0 ) ;
my ( $ sec , $ min , $ hour , $ mday , $ mon , $ year , $ wday , $ yday , $ isdst ) = localtime ( time ) ;
2020-09-29 20:56:15 +00:00
my $ cts = fhemTimeLocal ( 0 , 0 , 0 , $ mday , $ mon , $ year ) ;
my $ offset = fhemTzOffset ( $ cts ) ;
my $ time = int ( ( $ cts + $ offset ) * 1000 ) ; # add Timestamp in Millisekunden and UTC
my $ call = 'https://www.sunnyportal.com/homemanager?t=' ;
if ( AttrVal ( $ name , "noHomeManager" , 0 ) ) { # Dashboard Seite abfragen wenn kein Home Manager vorhanden ist
$ call = 'https://www.sunnyportal.com/Dashboard?t=' ;
}
2020-06-17 09:16:57 +00:00
( $ errstate , $ state , $ reread , $ retry ) = __dispatchGet ( { name = > $ name ,
ua = > $ ua ,
2020-09-29 20:56:15 +00:00
call = > $ call . $ time ,
2020-06-17 09:16:57 +00:00
tag = > "liveData" ,
state = > $ state ,
fnaref = > [ qw( extractLiveData ) ] ,
addon = > "" ,
daref = > $ daref
} ) ;
return ( $ errstate , $ state , $ reread , $ retry ) ;
}
2020-06-20 08:02:04 +00:00
################################################################
# Abruf Wetterdaten
################################################################
sub _getWeatherData { ## 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 ) ;
( $ errstate , $ state ) = __dispatchGet ( { name = > $ name ,
ua = > $ ua ,
call = > 'https://www.sunnyportal.com/Dashboard/Weather' ,
tag = > "weatherData" ,
state = > $ state ,
fnaref = > [ qw( extractWeatherData ) ] ,
addon = > "" ,
daref = > $ daref
} ) ;
return ( $ errstate , $ state , $ reread , $ retry ) ;
}
################################################################
# Abruf Anlagen Stammdaten
################################################################
2020-06-20 11:18:13 +00:00
sub _getPlantMasterData { ## no critic "not used"
2020-06-20 08:02:04 +00:00
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 ) ;
( $ errstate , $ state ) = __dispatchGet ( { name = > $ name ,
ua = > $ ua ,
call = > 'https://www.sunnyportal.com/HoMan/Forecast/LoadRecommendationData' ,
2020-06-20 11:18:13 +00:00
tag = > "plantMasterData" ,
2020-06-20 08:02:04 +00:00
state = > $ state ,
2020-06-20 11:18:13 +00:00
fnaref = > [ qw( extractPlantMasterData ) ] ,
2020-06-20 08:02:04 +00:00
addon = > "" ,
daref = > $ daref
} ) ;
return ( $ errstate , $ state , $ reread , $ retry ) ;
}
################################################################
# Abruf Consumer Stammdaten
################################################################
sub _getConsumerMasterdata { ## 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 ) ;
( $ errstate , $ state ) = __dispatchGet ( { name = > $ name ,
ua = > $ ua ,
call = > 'https://www.sunnyportal.com/Homan/ConsumerBalance/GetLiveProxyValues' ,
tag = > "consumerMasterdata" ,
state = > $ state ,
fnaref = > [ qw( extractConsumerMasterdata ) ] ,
2020-12-01 20:51:52 +00:00
addon = > "noJSONdata" , # wenn kein Consumer vorhanden wird kein JSON geliefert !
2020-06-20 08:02:04 +00:00
daref = > $ daref
} ) ;
return ( $ errstate , $ state , $ reread , $ retry ) ;
}
################################################################
# Abruf Consumer current Data
################################################################
sub _getConsumerCurrData { ## 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 ) ;
2020-12-01 20:51:52 +00:00
# einfache Verbraucher (z.B Schaltsteckdosen) abfragen
( $ errstate , $ state ) = __dispatchGet ( { name = > $ name ,
ua = > $ ua ,
call = > 'https://www.sunnyportal.com/Homan/ConsumerBalance/GetLiveProxyValues' ,
tag = > "consumerCurrentdata" ,
state = > $ state ,
fnaref = > [ qw( extractConsumerCurrentdata ) ] ,
addon = > "" ,
daref = > $ daref
2020-06-20 08:02:04 +00:00
} ) ;
2020-12-01 20:51:52 +00:00
return ( $ errstate , $ state , $ reread , $ retry ) if ( $ errstate ) ;
# Verbraucher mit Energymanagement (z.B. SMA EV Charger SUSyID=315 abfragen)
my $ hash = $ defs { $ name } ;
for my $ key ( keys % { $ hash - > { HELPER } { CONSUMER } } ) {
my $ susyid = $ hash - > { HELPER } { CONSUMER } { $ key } { SUSyID } ;
next if ( $ susyid != 315 ) ; # nur Auswertung SMA EV Charger
my $ oid = $ hash - > { HELPER } { CONSUMER } { $ key } { ConsumerOid } ;
my $ d = $ hash - > { HELPER } { CONSUMER } { $ key } { DeviceName } ;
$ paref - > { state } = $ state ;
$ paref - > { oid } = $ oid ;
$ paref - > { addon } = "$d:noJSONdata" ; # das gemanagte Device in addon mitgeben, noJSONdata -> kein no JSON Fehler auswerten !!
( $ errstate , $ state ) = _getConsumerEnergySetting ( $ paref ) ;
}
2020-06-20 08:02:04 +00:00
return ( $ errstate , $ state , $ reread , $ retry ) ;
}
################################################################
# Abruf Consumer Tagesdaten
################################################################
sub _getConsumerDayData { ## 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
2020-12-01 20:51:52 +00:00
2020-06-20 08:02:04 +00:00
my $ hash = $ defs { $ name } ;
my ( $ reread , $ retry , $ errstate ) = ( 0 , 0 , 0 ) ;
if ( ! $ hash - > { HELPER } { PLANTOID } ) {
$ errstate = 1 ;
$ state = qq{ The consumer data cannot be retrieved because the plant ID isn't set. } ;
2020-12-01 20:51:52 +00:00
Log3 ( $ name , 2 , "$name - $state" ) ;
2020-06-20 08:02:04 +00:00
return ( $ errstate , $ state , $ reread , $ retry ) ;
}
my $ PlantOid = $ hash - > { HELPER } { PLANTOID } ;
my $ dds = ( split ( /\s+/x , TimeNow ( ) ) ) [ 0 ] ;
my $ dde = ( split ( /\s+/x , FmtDateTime ( time ( ) + 86400 ) ) ) [ 0 ] ;
2020-12-02 09:37:43 +00:00
my $ ccdd = 'https://www.sunnyportal.com/Homan/ConsumerBalance/GetMeasuredValues?IntervalId=2&PlantOid=' . $ PlantOid . '&StartTime=' . $ dds . '&EndTime=' . $ dde . '' ;
2020-06-20 08:02:04 +00:00
# Energiedaten aktueller Tag
2020-10-11 07:40:33 +00:00
Log3 ( $ name , 4 , "$name - getting consumer energy data of current day" ) ;
2020-06-20 08:02:04 +00:00
Log3 ( $ name , 4 , "$name - Request date -> start: $dds, end: $dde" ) ;
Log3 ( $ name , 5 , "$name - Request consumer current day data string ->\n$ccdd" ) ;
( $ errstate , $ state ) = __dispatchGet ( { name = > $ name ,
ua = > $ ua ,
call = > $ ccdd ,
tag = > "consumerDayData" ,
state = > $ state ,
fnaref = > [ qw( extractConsumerHistData ) ] ,
addon = > "day" ,
daref = > $ daref
} ) ;
return ( $ errstate , $ state , $ reread , $ retry ) ;
}
################################################################
# Abruf Consumer Monatsdaten
################################################################
sub _getConsumerMonthData { ## 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 $ hash = $ defs { $ name } ;
my ( $ reread , $ retry , $ errstate ) = ( 0 , 0 , 0 ) ;
if ( ! $ hash - > { HELPER } { PLANTOID } ) {
$ errstate = 1 ;
$ state = qq{ The consumer data cannot be retrieved because the plant ID isn't set. } ;
2020-12-01 20:51:52 +00:00
Log3 ( $ name , 2 , "$name - $state" ) ;
2020-06-20 08:02:04 +00:00
return ( $ errstate , $ state , $ reread , $ retry ) ;
}
my $ PlantOid = $ hash - > { HELPER } { PLANTOID } ;
my $ dds = ( split ( /\s+/x , TimeNow ( ) ) ) [ 0 ] ;
my $ dde = ( split ( /\s+/x , FmtDateTime ( time ( ) + 86400 ) ) ) [ 0 ] ;
my ( $ mds , $ me , $ ye , $ mde ) ;
if ( $ dds =~ /(.*)-(.*)-(.*)/x ) {
$ mds = "$1-$2-01" ;
$ me = ( ( $ 2 + 1 ) <= 12 ) ? $ 2 + 1 : 1 ;
$ me = sprintf ( "%02d" , $ me ) ;
$ ye = ( $ 2 > $ me ) ? $ 1 + 1 : $ 1 ;
$ mde = "$ye-$me-01" ;
}
2020-12-02 09:37:43 +00:00
my $ ccmd = 'https://www.sunnyportal.com/Homan/ConsumerBalance/GetMeasuredValues?IntervalId=4&PlantOid=' . $ PlantOid . '&StartTime=' . $ mds . '&EndTime=' . $ mde . '' ;
2020-06-20 08:02:04 +00:00
# Energiedaten aktueller Monat
2020-10-11 07:40:33 +00:00
Log3 ( $ name , 4 , "$name - getting consumer energy data of current month" ) ;
2020-06-20 08:02:04 +00:00
Log3 ( $ name , 4 , "$name - Request date -> start: $mds, end: $mde" ) ;
Log3 ( $ name , 5 , "$name - Request consumer current month data string ->\n$ccmd" ) ;
( $ errstate , $ state ) = __dispatchGet ( { name = > $ name ,
ua = > $ ua ,
call = > $ ccmd ,
tag = > "consumerMonthData" ,
state = > $ state ,
fnaref = > [ qw( extractConsumerHistData ) ] ,
addon = > "month" ,
daref = > $ daref
} ) ;
return ( $ errstate , $ state , $ reread , $ retry ) ;
}
################################################################
# Abruf Consumer Jahresdaten
################################################################
sub _getConsumerYearData { ## 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 $ hash = $ defs { $ name } ;
my ( $ reread , $ retry , $ errstate ) = ( 0 , 0 , 0 ) ;
if ( ! $ hash - > { HELPER } { PLANTOID } ) {
$ errstate = 1 ;
$ state = qq{ The consumer data cannot be retrieved because of the plant ID isn't set. } ;
2020-12-01 20:51:52 +00:00
Log3 ( $ name , 2 , "$name - $state" ) ;
2020-06-20 08:02:04 +00:00
return ( $ errstate , $ state , $ reread , $ retry ) ;
}
my $ PlantOid = $ hash - > { HELPER } { PLANTOID } ;
my $ dds = ( split ( /\s+/x , TimeNow ( ) ) ) [ 0 ] ;
my $ dde = ( split ( /\s+/x , FmtDateTime ( time ( ) + 86400 ) ) ) [ 0 ] ;
my ( $ mds , $ me , $ ye , $ mde , $ yds , $ yde ) ;
if ( $ dds =~ /(.*)-(.*)-(.*)/x ) {
$ mds = "$1-$2-01" ;
$ me = ( ( $ 2 + 1 ) <= 12 ) ? $ 2 + 1 : 1 ;
$ me = sprintf ( "%02d" , $ me ) ;
$ ye = ( $ 2 > $ me ) ? $ 1 + 1 : $ 1 ;
$ mde = "$ye-$me-01" ;
$ yds = "$1-01-01" ;
$ yde = ( $ 1 + 1 ) . "-01-01" ;
}
2020-12-02 09:37:43 +00:00
my $ ccyd = 'https://www.sunnyportal.com/Homan/ConsumerBalance/GetMeasuredValues?IntervalId=5&PlantOid=' . $ PlantOid . '&StartTime=' . $ yds . '&EndTime=' . $ yde . '' ;
2020-06-20 08:02:04 +00:00
# Energiedaten aktuelles Jahr
2020-10-11 07:40:33 +00:00
Log3 ( $ name , 4 , "$name - getting consumer energy data of current year" ) ;
2020-06-20 08:02:04 +00:00
Log3 ( $ name , 4 , "$name - Request date -> start: $yds, end: $yde" ) ;
Log3 ( $ name , 5 , "$name - Request consumer current year data string ->\n$ccyd" ) ;
( $ errstate , $ state ) = __dispatchGet ( { name = > $ name ,
ua = > $ ua ,
call = > $ ccyd ,
tag = > "consumerYearData" ,
state = > $ state ,
fnaref = > [ qw( extractConsumerHistData ) ] ,
addon = > "year" ,
daref = > $ daref
} ) ;
return ( $ errstate , $ state , $ reread , $ retry ) ;
}
################################################################
# Abruf Vorhersage Daten
################################################################
sub _getForecastData { ## 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 ) ;
( $ errstate , $ state ) = __dispatchGet ( { name = > $ name ,
ua = > $ ua ,
call = > 'https://www.sunnyportal.com/HoMan/Forecast/LoadRecommendationData' ,
tag = > "forecastData" ,
state = > $ state ,
fnaref = > [ qw( extractForecastData extractConsumerPlanData ) ] ,
addon = > "" ,
daref = > $ daref
} ) ;
return ( $ errstate , $ state , $ reread , $ retry ) ;
}
2020-06-27 10:15:45 +00:00
################################################################
# Abruf Statistik Daten Day
# (anchorTime beachten !)
################################################################
2020-08-06 19:39:34 +00:00
sub _getBalanceDayData { ## no critic "not used"
2020-10-11 19:25:54 +00:00
my $ paref = shift ;
my $ name = $ paref - > { name } ;
my $ ua = $ paref - > { ua } ; # LWP Useragent
my $ state = $ paref - > { state } ;
my $ daref = $ paref - > { daref } ; # Referenz zum Datenarray
2020-06-27 10:15:45 +00:00
2020-08-06 19:39:34 +00:00
my ( $ reread , $ retry , $ errstate ) = ( 0 , 0 , 0 ) ;
2020-08-08 05:16:54 +00:00
2020-10-11 19:25:54 +00:00
my @ bd = split /\s+/x , AttrVal ( $ name , "balanceDay" , "current" ) ;
my $ tag = "balanceDayData" ;
2020-08-08 10:29:35 +00:00
for my $ bal ( @ bd ) {
my ( $ y , $ m , $ d ) ;
2020-10-11 19:25:54 +00:00
my $ addon = "Day_" ;
2020-08-08 10:29:35 +00:00
2020-10-10 19:05:38 +00:00
if ( $ bal !~ /current/ixms ) {
2020-08-08 10:29:35 +00:00
( $ y , $ m , $ d ) = $ bal =~ /(\d{4})-(\d{2})-(\d{2})/x ;
if ( ! $ y || ! $ m || ! $ d ) {
Log3 ( $ name , 2 , qq{ $name - The attribute "balanceDay" value "$bal" is ignored. A valid date with form "YYYY-MM-DD" is needed } ) ;
next ;
}
2020-10-11 19:25:54 +00:00
$ addon . = $ bal ;
2020-10-11 07:40:33 +00:00
$ y -= 1900 ;
$ m -= 1 ;
}
else {
my $ mp = ( split "-" , $ bal ) [ 1 ] // 0 ; # Multiplikator: z.B. current-1 -> 1
my $ time = time - ( $ mp * 86400 ) ;
2020-10-10 19:05:38 +00:00
( undef , undef , undef , $ d , $ m , $ y ) = localtime ( $ time ) ;
2020-10-11 19:25:54 +00:00
my $ addon1 = ( $ y + 1900 ) . "-" . ( sprintf "%02d" , $ m + 1 ) . "-" . sprintf "%02d" , $ d ;
2020-10-11 07:40:33 +00:00
2020-10-11 19:25:54 +00:00
my $ params = {
name = > $ name ,
bal = > $ bal ,
tag = > $ tag ,
daref = > $ daref ,
addon = > $ addon ,
addon1 = > $ addon1 ,
} ;
$ addon = createDateAddon ( $ params ) ;
2020-08-08 10:29:35 +00:00
}
eval { timelocal ( 0 , 0 , 0 , $ d , $ m , $ y ) } or do { $ state = ( split ( " at" , $@ ) ) [ 0 ] ;
$ errstate = 1 ;
Log3 ( $ name , 2 , "$name - ERROR - invalid date/time format in attribute 'balanceDay' detected: $state" ) ;
return ( $ errstate , $ state , $ reread , $ retry ) ;
} ;
2020-10-11 19:25:54 +00:00
Log3 ( $ name , 4 , "$name - retrieve $tag " . ( $ y + 1900 ) . "-" . ( sprintf "%02d" , $ m + 1 ) . "-" . sprintf "%02d" , $ d ) ;
2020-06-20 08:02:04 +00:00
2020-10-11 19:25:54 +00:00
my $ cts = fhemTimeLocal ( 0 , 0 , 0 , $ d , $ m , $ y ) ;
my $ offset = fhemTzOffset ( $ cts ) ;
my $ anchort = int ( $ cts + $ offset ) ; # anchorTime in UTC -> abzurufendes Datum
2020-08-08 10:29:35 +00:00
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 } } ;
( $ errstate , $ state ) = __dispatchPost ( { name = > $ name ,
ua = > $ ua ,
call = > 'https://www.sunnyportal.com/FixedPages/HoManEnergyRedesign.aspx/GetLegendWithValues' ,
2020-10-11 19:25:54 +00:00
tag = > $ tag ,
2020-08-08 10:29:35 +00:00
state = > $ state ,
fnaref = > [ qw( extractStatisticData ) ] ,
fields = > \ % fields ,
content = > $ cont ,
addon = > $ addon ,
daref = > $ daref
2020-10-11 19:25:54 +00:00
} ) ;
2020-08-08 10:29:35 +00:00
}
2020-06-20 08:02:04 +00:00
return ( $ errstate , $ state , $ reread , $ retry ) ;
}
2020-07-02 19:45:08 +00:00
################################################################
# Abruf Statistik Daten Month
# (anchorTime beachten !)
################################################################
sub _getBalanceMonthData { ## no critic "not used"
2020-10-11 19:25:54 +00:00
my $ paref = shift ;
my $ name = $ paref - > { name } ;
my $ ua = $ paref - > { ua } ; # LWP Useragent
my $ state = $ paref - > { state } ;
my $ daref = $ paref - > { daref } ; # Referenz zum Datenarray
2020-07-02 19:45:08 +00:00
2020-08-06 19:39:34 +00:00
my ( $ reread , $ retry , $ errstate ) = ( 0 , 0 , 0 ) ;
2020-10-11 19:25:54 +00:00
my @ bd = split /\s+/x , AttrVal ( $ name , "balanceMonth" , "current" ) ;
my $ tag = "balanceMonthData" ;
2020-08-06 19:39:34 +00:00
2020-08-08 10:29:35 +00:00
for my $ bal ( @ bd ) {
my ( $ y , $ m ) ;
2020-10-11 19:25:54 +00:00
my $ addon = "Month_" ;
2020-08-08 10:29:35 +00:00
2020-10-11 07:40:33 +00:00
if ( $ bal !~ /current/ixms ) {
2020-08-08 10:29:35 +00:00
( $ y , $ m ) = $ bal =~ /^(\d{4})-(\d{2})$/x ;
if ( ! $ y || ! $ m ) {
Log3 ( $ name , 2 , qq{ $name - The attribute "balanceMonth" value "$bal" is ignored. A valid date with form "YYYY-MM" is needed } ) ;
next ;
}
2020-10-11 19:25:54 +00:00
$ addon . = $ bal ;
2020-10-11 07:40:33 +00:00
$ y -= 1900 ;
$ m -= 1 ;
}
else {
my $ mp = ( split "-" , $ bal ) [ 1 ] // 0 ;
my $ yc = int ( $ mp / 12 ) ; # Anzahl der Jahre
my $ mc = $ mp % 12 ; # Anzahl Restmonate
2020-10-11 19:25:54 +00:00
$ y = ( localtime ( time ) ) [ 5 ] ;
$ y -= $ yc ;
$ m = ( localtime ( time ) ) [ 4 ] ;
2020-10-11 07:40:33 +00:00
if ( $ m - $ mc < 1 ) {
$ m = 12 - abs ( $ m - $ mc ) ;
$ y - - ;
}
else {
$ m = $ m - $ mc ;
}
2020-10-11 19:25:54 +00:00
my $ addon1 = ( $ y + 1900 ) . "-" . sprintf "%02d" , $ m + 1 ;
2020-10-11 07:40:33 +00:00
2020-10-11 19:25:54 +00:00
my $ params = {
name = > $ name ,
bal = > $ bal ,
tag = > $ tag ,
daref = > $ daref ,
addon = > $ addon ,
addon1 = > $ addon1 ,
} ;
$ addon = createDateAddon ( $ params ) ;
2020-08-08 10:29:35 +00:00
}
2020-11-03 18:13:28 +00:00
2020-10-31 16:17:33 +00:00
my $ dim = daysInMonth ( $ m + 1 , $ y + 1900 ) ; # errechnet wieviel Tage der gegebene Monat hat
2020-07-02 19:45:08 +00:00
2020-10-31 15:51:25 +00:00
eval { timelocal ( 0 , 0 , 0 , $ dim , $ m , $ y ) } or do { $ state = ( split ( " at" , $@ ) ) [ 0 ] ;
$ errstate = 1 ;
Log3 ( $ name , 2 , "$name - ERROR - invalid date/time format in attribute 'balanceMonth' detected: $state" ) ;
return ( $ errstate , $ state , $ reread , $ retry ) ;
} ;
2020-08-08 10:29:35 +00:00
2020-10-11 19:25:54 +00:00
Log3 ( $ name , 4 , "$name - retrieve $tag " . ( $ y + 1900 ) . "-" . sprintf "%02d" , $ m + 1 ) ;
2020-10-31 15:51:25 +00:00
my $ cts = fhemTimeLocal ( 0 , 0 , 0 , $ dim , $ m , $ y ) ;
2020-10-11 19:25:54 +00:00
my $ offset = fhemTzOffset ( $ cts ) ;
my $ anchort = int ( $ cts + $ offset ) ; # anchorTime in UTC -> abzurufendes Datum
2020-08-08 10:29:35 +00:00
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' ,
2020-10-11 19:25:54 +00:00
tag = > $ tag ,
2020-08-08 10:29:35 +00:00
state = > $ state ,
fnaref = > [ qw( extractStatisticData ) ] ,
fields = > \ % fields ,
content = > $ cont ,
addon = > $ addon ,
daref = > $ daref
2020-10-11 19:25:54 +00:00
} ) ;
2020-08-08 10:29:35 +00:00
}
2020-07-02 19:45:08 +00:00
return ( $ errstate , $ state , $ reread , $ retry ) ;
}
################################################################
# Abruf Statistik Daten Year
# (anchorTime beachten !)
################################################################
sub _getBalanceYearData { ## no critic "not used"
2020-10-11 19:25:54 +00:00
my $ paref = shift ;
my $ name = $ paref - > { name } ;
my $ ua = $ paref - > { ua } ; # LWP Useragent
my $ state = $ paref - > { state } ;
my $ daref = $ paref - > { daref } ; # Referenz zum Datenarray
2020-07-02 19:45:08 +00:00
my ( $ reread , $ retry , $ errstate ) = ( 0 , 0 , 0 ) ;
2020-10-11 19:25:54 +00:00
my @ bd = split /\s+/x , AttrVal ( $ name , "balanceYear" , "current" ) ;
my $ tag = "balanceYearData" ;
2020-08-06 19:39:34 +00:00
2020-08-08 10:29:35 +00:00
for my $ bal ( @ bd ) {
my $ y ;
2020-10-11 19:25:54 +00:00
my $ addon = "Year_" ;
2020-08-08 10:29:35 +00:00
2020-10-11 07:40:33 +00:00
if ( $ bal !~ /current/ixms ) {
2020-08-08 10:29:35 +00:00
( $ y ) = $ bal =~ /^(\d{4})$/x ;
if ( ! $ y ) {
Log3 ( $ name , 2 , qq{ $name - The attribute "balanceYear" value "$bal" is ignored. A valid date with form "YYYY" is needed } ) ;
next ;
}
2020-10-11 19:25:54 +00:00
$ addon . = $ bal ;
2020-10-11 07:40:33 +00:00
$ y -= 1900 ;
}
else {
2020-10-11 19:25:54 +00:00
my $ mp = ( split "-" , $ bal ) [ 1 ] // 0 ;
$ y = ( localtime ( time ) ) [ 5 ] ;
$ y -= $ mp ;
my $ addon1 = $ y + 1900 ;
2020-10-11 07:40:33 +00:00
2020-10-11 19:25:54 +00:00
my $ params = {
name = > $ name ,
bal = > $ bal ,
tag = > $ tag ,
daref = > $ daref ,
addon = > $ addon ,
addon1 = > $ addon1 ,
} ;
$ addon = createDateAddon ( $ params ) ;
2020-08-08 10:29:35 +00:00
}
eval { timelocal ( 0 , 0 , 0 , 1 , 1 , $ y ) } or do { $ state = ( split ( " at" , $@ ) ) [ 0 ] ;
$ errstate = 1 ;
Log3 ( $ name , 2 , "$name - ERROR - invalid date/time format in attribute 'balanceYear' detected: $state" ) ;
return ( $ errstate , $ state , $ reread , $ retry ) ;
} ;
2020-10-11 19:25:54 +00:00
Log3 ( $ name , 4 , "$name - retrieve $tag " . ( $ y + 1900 ) ) ;
2020-08-08 10:29:35 +00:00
2020-10-11 19:25:54 +00:00
my $ cts = fhemTimeLocal ( 0 , 0 , 0 , 1 , 1 , $ y ) ;
my $ offset = fhemTzOffset ( $ cts ) ;
my $ anchort = int ( $ cts + $ offset ) ; # anchorTime in UTC -> abzurufendes Datum
2020-08-08 10:29:35 +00:00
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' ,
2020-10-11 19:25:54 +00:00
tag = > $ tag ,
2020-08-08 10:29:35 +00:00
state = > $ state ,
fnaref = > [ qw( extractStatisticData ) ] ,
fields = > \ % fields ,
content = > $ cont ,
addon = > $ addon ,
daref = > $ daref
} ) ;
}
2020-07-02 19:45:08 +00:00
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 ) ;
}
2020-06-20 08:02:04 +00:00
################################################################
# Abruf Anlagen Logbuch
################################################################
sub _getPlantLogbook { ## 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 $ hash = $ defs { $ name } ;
my ( $ reread , $ retry , $ errstate ) = ( 0 , 0 , 0 ) ;
if ( ! $ hash - > { HELPER } { PLANTOID } ) {
$ errstate = 1 ;
$ state = qq{ The logbook cannot be retrieved because of the plant ID isn't set. } ;
Log3 $ name , 2 , "$name - $state" ;
return ( $ errstate , $ state , $ reread , $ retry ) ;
}
my $ PlantOid = $ hash - > { HELPER } { PLANTOID } ;
my $ msgtypes = AttrVal ( $ name , "plantLogbookTypes" , "Warning,Disturbance,Error" ) ; # möglich: Warning,Info,Disturbance,Error
my $ appstate = AttrVal ( $ name , "plantLogbookApprovalState" , "Any" ) ;
my $ date = ( split ( /\s+/x , TimeNow ( ) ) ) [ 0 ] ;
my $ call = 'https://www.sunnyportal.com/Plants/' . $ PlantOid . '/Log/Get?MessageTypes=' . $ msgtypes . '&ApprovalState=' . $ appstate . '&Device=None&MaxDateTime=' . $ date . '&Ticks=0' ;
Log3 ( $ name , 4 , "$name - Retrieving the logbook data up to the date: $date" ) ;
( $ errstate , $ state ) = __dispatchGet ( { name = > $ name ,
ua = > $ ua ,
call = > $ call ,
tag = > "plantLogbook" ,
state = > $ state ,
fnaref = > [ qw( extractPlantLogbook ) ] ,
addon = > "" ,
daref = > $ daref
} ) ;
return ( $ errstate , $ state , $ reread , $ retry ) ;
}
2020-11-03 18:13:28 +00:00
################################################################
# Detailanzeige einschalten
# vor dem eigentlichen Datenabruf
################################################################
2020-11-04 16:55:41 +00:00
sub _detailViewOn {
2020-11-03 18:13:28 +00:00
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 $ tag = "detailViewSwitch" ;
my % fields = ( "Content-Type" = > "application/json; charset=utf-8" ) ;
2020-11-03 19:47:39 +00:00
my $ cont = qq{ { "showDetailMode":true } } ;
2020-11-03 18:13:28 +00:00
( $ errstate , $ state ) = __dispatchPost ( { name = > $ name ,
ua = > $ ua ,
call = > 'https://www.sunnyportal.com/FixedPages/HoManEnergyRedesign.aspx/UpdateDisplayOption' ,
tag = > $ tag ,
state = > $ state ,
fnaref = > [ qw( extractHelperData ) ] ,
fields = > \ % fields ,
content = > $ cont ,
addon = > "" ,
daref = > $ daref
} ) ;
return ( $ errstate , $ state , $ reread , $ retry ) ;
}
2020-12-01 20:51:52 +00:00
################################################################
# Abruf Settings eines Consumers mit SUSyID 315
# (z.B. nach HTML 302 Weiterleitung)
################################################################
sub _getConsumerEnergySetting { ## 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 $ oid = $ paref - > { oid } ; # Consumer oid
my $ addon = $ paref - > { addon } ; # optionales AddOn - hier das managed Device
my ( $ reread , $ retry , $ errstate ) = ( 0 , 0 , 0 ) ;
( $ errstate , $ state ) = __dispatchGet ( { name = > $ name ,
ua = > $ ua ,
call = > 'https://www.sunnyportal.com/HoMan/Consumer/Semp/' . $ oid ,
tag = > "consumerCurrentdata" ,
state = > $ state ,
fnaref = > [ qw( extractConsumerEnergySetting ) ] ,
addon = > $ addon ,
daref = > $ daref
} ) ;
return ( $ errstate , $ state , $ reread , $ retry ) ;
}
2020-06-10 21:43:56 +00:00
################################################################
2020-06-11 12:18:13 +00:00
# Dispatcher GET
2020-06-10 21:43:56 +00:00
################################################################
2020-06-17 09:16:57 +00:00
sub __dispatchGet {
2020-06-10 21:43:56 +00:00
my $ paref = shift ;
my $ name = $ paref - > { name } ;
my $ state = $ paref - > { state } ;
2020-06-11 12:18:13 +00:00
my $ fnref = $ paref - > { fnaref } ; # Referenz zu Array der aufzurufenden Funktion(en) zur Datenextraktion
2020-12-01 20:51:52 +00:00
my $ addon = $ paref - > { addon } ; # optionales Addon für aufzurufende Funktion oder spezielle Steuerungen
2020-06-10 21:43:56 +00:00
my $ daref = $ paref - > { daref } ; # Referenz zum Datenarray
2020-12-01 20:51:52 +00:00
2020-06-10 21:43:56 +00:00
my $ hash = $ defs { $ name } ;
my ( $ reread , $ retry , $ errstate ) = ( 0 , 0 , 0 ) ;
2020-12-01 20:51:52 +00:00
my ( $ data , $ data_cont ) = ___getData ( $ paref ) ;
$ paref - > { data } = $ data ;
$ paref - > { errstate } = $ errstate ;
2020-06-10 21:43:56 +00:00
2020-12-01 20:51:52 +00:00
( $ reread , $ retry , $ errstate , $ state ) = ___analyzeData ( $ paref ) ;
2020-06-10 21:43:56 +00:00
2020-06-11 12:18:13 +00:00
return ( $ errstate , $ state , $ reread , $ retry ) if ( $ errstate || $ reread || $ retry ) ;
if ( $ data_cont && $ data_cont !~ m/undefined/ix ) {
my @ func = @$ fnref ;
no strict "refs" ; ## no critic 'NoStrict'
for my $ fn ( @ func ) {
2020-12-01 20:51:52 +00:00
& { $ fn } ( $ hash , $ daref , $ data_cont , $ addon , $ data ) ;
2020-06-11 12:18:13 +00:00
}
use strict "refs" ;
}
return ( $ errstate , $ state , $ reread , $ retry ) ;
}
################################################################
# Dispatcher POST
################################################################
2020-06-17 09:16:57 +00:00
sub __dispatchPost {
2020-06-11 12:18:13 +00:00
my $ paref = shift ;
my $ name = $ paref - > { name } ;
my $ ua = $ paref - > { ua } ; # LWP Useragent
my $ tag = $ paref - > { tag } ; # Kennzeichen der abzurufenen Daten
my $ state = $ paref - > { state } ;
my $ fnref = $ paref - > { fnaref } ; # Referenz zu Array der aufzurufenden Funktion(en) zur Datenextraktion
2020-12-01 20:51:52 +00:00
my $ addon = $ paref - > { addon } ; # optionales Addon für aufzurufende Funktion
2020-06-11 12:18:13 +00:00
my $ daref = $ paref - > { daref } ; # Referenz zum Datenarray
2020-12-01 20:51:52 +00:00
2020-06-11 12:18:13 +00:00
my $ hash = $ defs { $ name } ;
my ( $ reread , $ retry , $ errstate ) = ( 0 , 0 , 0 ) ;
2020-12-01 20:51:52 +00:00
my ( $ data , $ data_cont ) = ___postData ( $ paref ) ;
$ paref - > { data } = $ data ;
$ paref - > { errstate } = $ errstate ;
2020-06-11 12:18:13 +00:00
2020-12-01 20:51:52 +00:00
( $ reread , $ retry , $ errstate , $ state ) = ___analyzeData ( $ paref ) ;
2020-06-11 12:18:13 +00:00
2020-06-10 21:43:56 +00:00
return ( $ errstate , $ state ) if ( $ errstate ) ;
if ( $ data_cont && $ data_cont !~ m/undefined/ix ) {
2020-06-11 12:18:13 +00:00
my @ func = @$ fnref ;
2020-12-01 20:51:52 +00:00
my $ params = {
hash = > $ hash ,
ua = > $ ua ,
daref = > $ daref ,
data_cont = > $ data_cont ,
data = > $ data ,
addon = > $ addon ,
tag = > $ tag
} ;
2020-06-10 21:43:56 +00:00
no strict "refs" ; ## no critic 'NoStrict'
2020-06-11 12:18:13 +00:00
for my $ fn ( @ func ) {
2020-12-01 20:51:52 +00:00
$ state = & { $ fn } ( $ params ) // $ state ;
2020-06-11 12:18:13 +00:00
}
2020-06-10 21:43:56 +00:00
use strict "refs" ;
}
2020-06-11 12:18:13 +00:00
return ( $ errstate , $ state , $ reread , $ retry ) ;
2020-06-10 21:43:56 +00:00
}
2020-06-09 07:18:56 +00:00
################################################################
2020-06-09 20:41:58 +00:00
# Standard Abruf Daten GET
2020-06-09 07:18:56 +00:00
################################################################
2020-06-17 09:16:57 +00:00
sub ___getData {
2020-12-01 20:51:52 +00:00
my $ paref = shift ;
my $ name = $ paref - > { name } ;
my $ ua = $ paref - > { ua } ; # LWP Useragent
my $ call = $ paref - > { call } ; # Seitenaufruf zur Datenquelle
my $ tag = $ paref - > { tag } ; # Kennzeichen der abzurufenen Daten
2020-06-09 07:18:56 +00:00
2020-06-11 21:35:57 +00:00
my $ v5d = AttrVal ( $ name , "verbose5Data" , "none" ) ;
my $ verbose = AttrVal ( $ name , "verbose" , 3 ) ;
2020-06-10 21:43:56 +00:00
my $ cont ;
2020-06-09 07:18:56 +00:00
2020-10-11 07:40:33 +00:00
Log3 ( $ name , 4 , "$name - getting $tag" ) ;
2020-06-11 21:35:57 +00:00
2020-06-20 08:02:04 +00:00
if ( $ verbose == 5 && $ v5d =~ /$tag/x ) {
2020-06-11 21:35:57 +00:00
$ ua - > add_handler ( request_send = > sub { shift - > dump ; return } ) ; # for debugging
$ ua - > add_handler ( response_done = > sub { shift - > dump ; return } ) ;
}
2020-06-09 07:18:56 +00:00
my $ data = $ ua - > get ( $ call ) ;
2020-11-22 20:53:43 +00:00
my $ dcont = $ data - > content ;
2020-12-01 20:51:52 +00:00
$ cont = eval { decode_json ( $ dcont ) } or do { $ cont = $ dcont } ; # Test JSON dekodieren und anzeigen
2020-06-09 07:18:56 +00:00
2020-06-20 08:02:04 +00:00
if ( $ v5d =~ /$tag/x ) {
2020-06-09 07:18:56 +00:00
Log3 ( $ name , 5 , "$name - Return Code: " . $ data - > code ) ;
Log3 ( $ name , 5 , "$name - $tag received:\n" . Dumper $ cont ) ;
}
2020-06-12 06:54:53 +00:00
$ ua - > remove_handler ( 'request_send' ) ;
$ ua - > remove_handler ( 'response_done' ) ;
2020-06-11 21:35:57 +00:00
2020-06-09 07:18:56 +00:00
return ( $ data , $ dcont ) ;
2019-03-03 20:08:04 +00:00
}
2020-06-09 20:41:58 +00:00
################################################################
# Standard Abruf Daten POST
################################################################
2020-06-17 09:16:57 +00:00
sub ___postData {
2020-06-09 20:41:58 +00:00
my $ paref = shift ;
my $ name = $ paref - > { name } ;
my $ ua = $ paref - > { ua } ;
my $ call = $ paref - > { call } ;
2020-12-01 20:51:52 +00:00
my $ fields = $ paref - > { fields } ; # Referenz zum Hash der zu übertragenden PUSH Header
my $ content = $ paref - > { content } ; # Content Daten für PUSH (String)
2020-06-09 20:41:58 +00:00
my $ tag = $ paref - > { tag } ;
2020-06-11 21:35:57 +00:00
2020-06-09 20:41:58 +00:00
my $ v5d = AttrVal ( $ name , "verbose5Data" , "none" ) ;
2020-06-11 21:35:57 +00:00
my $ verbose = AttrVal ( $ name , "verbose" , 3 ) ;
2020-06-09 20:41:58 +00:00
my $ cont ;
2020-10-11 07:40:33 +00:00
Log3 ( $ name , 4 , "$name - getting $tag" ) ;
2020-06-11 21:35:57 +00:00
2020-06-20 08:02:04 +00:00
if ( $ verbose == 5 && $ v5d =~ /$tag/x ) {
2020-06-11 21:35:57 +00:00
$ ua - > add_handler ( request_send = > sub { shift - > dump ; return } ) ; # for debugging
$ ua - > add_handler ( response_done = > sub { shift - > dump ; return } ) ;
}
2020-06-09 20:41:58 +00:00
2020-12-01 20:51:52 +00:00
my $ data = $ ua - > post ( $ call , %$ fields , Content = > $ content ) ;
my $ dcont = $ data - > decoded_content ;
$ cont = eval { decode_json ( $ dcont ) } or do { $ cont = $ dcont } ; # Test JSON dekodieren und anzeigen
2020-06-09 20:41:58 +00:00
2020-06-20 08:02:04 +00:00
if ( $ v5d =~ /$tag/x ) {
2020-06-09 20:41:58 +00:00
Log3 ( $ name , 5 , "$name - Return Code: " . $ data - > code ) ;
Log3 ( $ name , 5 , "$name - $tag received:\n" . Dumper $ cont ) ;
}
2020-06-12 06:54:53 +00:00
$ ua - > remove_handler ( 'request_send' ) ;
$ ua - > remove_handler ( 'response_done' ) ;
2020-06-11 21:35:57 +00:00
2020-06-09 20:41:58 +00:00
return ( $ data , $ dcont ) ;
}
2020-06-10 21:43:56 +00:00
################################################################
# analysiere abgerufene Daten
################################################################
2020-06-20 08:02:04 +00:00
sub ___analyzeData { ## no critic 'complexity'
2020-06-10 21:43:56 +00:00
my $ paref = shift ;
my $ name = $ paref - > { name } ;
my $ errstate = $ paref - > { errstate } ;
my $ state = $ paref - > { state } ;
2020-11-03 21:13:03 +00:00
my $ ua = $ paref - > { ua } ;
2020-06-10 21:43:56 +00:00
my $ ad = $ paref - > { data } ;
2020-12-01 20:51:52 +00:00
my $ addon = $ paref - > { addon } // "" ; # addon kann Optionen zur Analysesteuerung enthalten
2020-06-10 21:43:56 +00:00
my $ hash = $ defs { $ name } ;
my ( $ reread , $ retry ) = ( 0 , 0 ) ;
my $ data = "" ;
2020-12-01 20:51:52 +00:00
my $ decerror = 0 ; # JSON Dekodierfehler
2020-06-10 21:43:56 +00:00
2020-11-03 22:18:42 +00:00
my $ v5d = AttrVal ( $ name , "verbose5Data" , "none" ) ;
2020-12-01 20:51:52 +00:00
my $ ad_content = encode ( "utf8" , $ ad - > decoded_content ) ;
my $ rescode = $ ad - > code ; # HTML Code der Antwort
2020-11-03 22:18:42 +00:00
my $ act = $ hash - > { HELPER } { RETRIES } ; # Index aktueller Wiederholungsversuch
my $ attstr = "Attempts read data again in $sleepretry s ... ($act of $maxretries)" ; # Log vorbereiten
my $ wm1e = qq{ Updating of the live data was interrupted } ;
my $ wm1d = qq{ Die Aktualisierung der Live-Daten wurde unterbrochen } ;
my $ wm2e = qq{ The current consumption could not be determined. The current purchased electricity is unknown } ;
my $ wm2d = qq{ Der aktuelle Verbrauch konnte nicht ermittelt werden. Der aktuelle Netzbezug ist unbekannt } ;
my $ em1e = qq{ Communication with the Sunny Home Manager is currently not possible } ;
my $ em1d = qq{ Die Kommunikation mit dem Sunny Home Manager ist zurzeit nicht m } ;
my $ em2e = qq{ The current data cannot be retrieved from the PV system. Check the cabling and configuration } ;
my $ em2d = qq{ Die aktuellen Daten .*? nicht von der Anlage abgerufen werden.*? Sie die Verkabelung und Konfiguration } ;
___extractCookie ( { ua = > $ ua ,
data = > $ ad ,
name = > $ name ,
} ) ;
2020-12-01 20:51:52 +00:00
$ data = eval { decode_json ( $ ad_content ) } or do { $ data = $ ad_content ;
$ decerror = 1 ;
} ;
2020-06-10 21:43:56 +00:00
2020-11-03 22:18:42 +00:00
my $ jsonerror = $ ad - > header ( 'Jsonerror' ) // "" ; # Portal meldet keine Verarbeitung des Reaquests möglich (z.B. Jahr 0000 zur Auswertung angefordert)
2020-11-03 21:13:03 +00:00
2020-08-08 10:29:35 +00:00
if ( $ jsonerror ) {
$ errstate = 1 ;
$ state = "SMA Portal failure: " . "Message -> " . $ data - > { Message } . ",\nStackTrace -> " . $ data - > { StackTrace } . ",\nExceptionType -> " . $ data - > { ExceptionType } ;
return ( $ reread , $ retry , $ errstate , $ state ) ;
}
2020-12-01 20:51:52 +00:00
if ( ! $ decerror && ref $ data eq "HASH" ) { # es wurde JSON empfangen und Ergebnis ist ein HASH
2020-06-10 21:43:56 +00:00
for my $ k ( keys % { $ data } ) {
my $ val = $ data - > { $ k } ;
next if ( ! defined $ val ) ;
2020-06-20 08:02:04 +00:00
my @ da ;
2020-06-10 21:43:56 +00:00
if ( ref $ val eq "ARRAY" ) {
2020-06-20 08:02:04 +00:00
for my $ a ( @ { $ val } ) {
push @ da , $ a if ( ! ref $ a ) ;
2020-06-10 21:43:56 +00:00
}
}
if ( ref $ val eq "HASH" ) {
for my $ b ( keys % { $ val } ) {
push @ da , $ b ;
}
}
$ val = join " " , @ da if ( @ da ) ;
2020-06-20 08:02:04 +00:00
2020-06-10 21:43:56 +00:00
if ( $ val && $ k !~ /__type/ix ) {
2020-06-20 08:02:04 +00:00
if ( $ k =~ m/WarningMessages/x && $ val =~ /$wm1e|$wm1d/ ) { ## no critic 'regular expression' # Regular expression without "/x" flag nicht anwenden !!!
Log3 ( $ name , 3 , "$name - Updating of the live data was interrupted. $attstr" ) ;
2020-06-10 21:43:56 +00:00
$ retry = 1 ;
return ( $ reread , $ retry , $ errstate , $ state ) ;
}
2020-06-20 08:02:04 +00:00
if ( $ k =~ m/WarningMessages/x && $ val =~ /$wm2e|$wm2d/ ) { ## 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" ) ;
2020-06-10 21:43:56 +00:00
$ retry = 1 ;
return ( $ reread , $ retry , $ errstate , $ state ) ;
}
2020-06-20 08:02:04 +00:00
if ( $ k =~ m/ErrorMessages/x && $ val =~ /$em1e|$em1d/ ) { ## no critic 'regular expression' # Regular expression without "/x" flag nicht anwenden !!!
2020-06-10 21:43:56 +00:00
# Energiedaten konnten nicht ermittelt werden, Daten neu lesen mit Zeitverzögerung
2020-06-20 08:02:04 +00:00
Log3 ( $ name , 3 , "$name - Communication with the Sunny Home Manager currently impossible. $attstr" ) ;
2020-06-10 21:43:56 +00:00
$ retry = 1 ;
return ( $ reread , $ retry , $ errstate , $ state ) ;
}
2020-06-20 08:02:04 +00:00
if ( $ k =~ m/ErrorMessages/x && $ val =~ /$em2e|$em2d/ ) { ## no critic 'regular expression' # Regular expression without "/x" flag nicht anwenden !!!
2020-06-10 21:43:56 +00:00
# Energiedaten konnten nicht ermittelt werden, Daten neu lesen mit Zeitverzögerung
2020-06-20 08:02:04 +00:00
Log3 ( $ name , 3 , "$name - The current data cannot be retrieved from the PV system. $attstr" ) ;
2020-06-10 21:43:56 +00:00
$ retry = 1 ;
return ( $ reread , $ retry , $ errstate , $ state ) ;
}
}
}
2020-12-01 20:51:52 +00:00
}
elsif ( ! $ decerror ) { # es wurde JSON empfangen aber Ergebnis ist KEIN HASH
Log3 ( $ name , 5 , "$name - decoded Content received: " . jboolmap ( $ data ) ) ;
}
2020-10-11 19:25:54 +00:00
else {
2020-12-01 20:51:52 +00:00
my $ njdat = encode ( "utf8" , $ ad - > as_string ) ;
2020-06-10 21:43:56 +00:00
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 // "" ) ;
}
2020-11-18 14:32:59 +00:00
2020-12-01 20:51:52 +00:00
$ njdat = encode ( "utf8" , $ ad - > decoded_content ) ;
2020-06-10 21:43:56 +00:00
Log3 ( $ name , 5 , "$name - No JSON Data received:\n " . $ njdat ) ;
2020-12-01 20:51:52 +00:00
if ( $ rescode != 302 && $ addon !~ /noJSONdata/x ) { # 302 -> HTTP-Antwort liefert zusätzlich eine URL im Header-Feld Location. Es soll eine zweite, ansonsten identische Anfrage an die in Location angegebene neue URL gestellt werden.
$ errstate = 1 ;
$ state = "ERROR - see logfile for further information" ;
}
2020-06-10 21:43:56 +00:00
}
return ( $ reread , $ retry , $ errstate , $ state ) ;
}
2020-11-03 22:18:42 +00:00
################################################################
# Cookie Daten analysieren & extrahieren
# Die extract_cookies()-Methode sucht im HTTP::Response-Objekt,
# das als Argument übergeben wird, nach Set-Cookie: und
# Set-Cookie2: Headern.
################################################################
sub ___extractCookie {
my $ paref = shift ;
my $ ua = $ paref - > { ua } ;
my $ data = $ paref - > { data } ; # empfangene Rohdaten
2020-11-04 16:55:41 +00:00
eval { $ ua - > cookie_jar - > extract_cookies ( $ data ) } or return ;
2020-11-03 22:18:42 +00:00
return ;
}
2019-03-03 20:08:04 +00:00
################################################################
## Verarbeitung empfangene Daten, setzen Readings
################################################################
2020-11-04 16:55:41 +00:00
sub ParseData {
2020-06-09 07:18:56 +00:00
my $ string = shift ;
my @ a = split ( "\\|" , $ string ) ;
my $ hash = $ defs { $ a [ 0 ] } ;
my $ name = $ hash - > { NAME } ;
my @ da = ( ) ;
2020-04-20 14:55:36 +00:00
2020-09-30 18:35:10 +00:00
my $ lc ;
2020-05-30 20:51:54 +00:00
2020-09-30 18:35:10 +00:00
my $ exceed = $ a [ 1 ] ;
my $ newcycle = $ a [ 2 ] ;
my $ errstate = $ a [ 3 ] ;
my $ getp = $ a [ 4 ] ;
my $ setp = $ a [ 5 ] ;
2020-05-30 20:51:54 +00:00
2020-05-31 16:03:46 +00:00
my $ ac = $ hash - > { HELPER } { ACTCYCLE } ;
my $ maxcycles = ( controlParams $ name ) [ 1 ] ;
2020-05-30 20:51:54 +00:00
2020-05-31 08:53:05 +00:00
if ( $ exceed ) {
2020-05-30 20:51:54 +00:00
delete ( $ hash - > { HELPER } { RUNNING_PID } ) ;
delcookiefile ( $ hash ) ;
$ hash - > { HELPER } { GETTER } = $ getp ;
2020-05-31 08:53:05 +00:00
$ hash - > { HELPER } { SETTER } = $ setp ;
2020-07-06 21:52:31 +00:00
CallInfo ( $ hash , 1 , 1 ) ; # neuer Versuch (nach Threshold exceed) im gleichen Cycle mit gelöschtem Cookie
2020-05-31 08:53:05 +00:00
return ;
}
2020-05-31 16:03:46 +00:00
if ( $ newcycle && $ ac < $ maxcycles ) {
2020-05-31 08:53:05 +00:00
delete ( $ hash - > { HELPER } { RUNNING_PID } ) ;
2020-06-26 11:11:23 +00:00
delcookiefile ( $ hash ) ;
2020-05-31 08:53:05 +00:00
$ hash - > { HELPER } { GETTER } = $ getp ;
2020-05-30 20:51:54 +00:00
$ hash - > { HELPER } { SETTER } = $ setp ;
$ hash - > { HELPER } { ACTCYCLE } + + ;
2020-07-06 21:52:31 +00:00
CallInfo ( $ hash , 1 , 0 ) ; # neuer Abrufcycle
2020-05-30 20:51:54 +00:00
return ;
2020-05-31 08:53:05 +00:00
}
2020-05-30 20:51:54 +00:00
2020-05-31 22:12:59 +00:00
# Laufzeit für einen Cycle berechnen
my $ btime = $ hash - > { HELPER } { CYCLEBTIME } ;
my $ etime = ( gettimeofday ( ) ) [ 0 ] ;
my $ cycles = $ hash - > { HELPER } { ACTCYCLE } ;
2020-07-06 21:52:31 +00:00
my $ ctime = int ( ( $ etime - $ btime ) / $ cycles ) ; # durchschnittliche Laufzeit für einen Zyklus
2020-05-31 22:12:59 +00:00
2020-09-30 18:35:10 +00:00
my $ state = decode_base64 ( $ a [ 6 ] ) ;
2020-04-20 14:55:36 +00:00
2020-06-09 07:18:56 +00:00
if ( $ a [ 7 ] ) {
$ lc = decode_base64 ( $ a [ 7 ] ) ;
@ da = split "###" , $ lc ;
}
2020-05-31 22:12:59 +00:00
2020-08-18 19:27:12 +00:00
deleteData ( $ hash , 1 ) if ( $ getp ne "none" ) ; # Daten nur löschen wenn Datenabruf (kein Verbraucher schalten)
2019-03-03 20:08:04 +00:00
readingsBeginUpdate ( $ hash ) ;
2020-06-09 07:18:56 +00:00
for my $ elem ( @ da ) {
my ( $ rn , $ rval ) = split ":" , $ elem , 2 ;
readingsBulkUpdate ( $ hash , $ rn , $ rval ) ;
2019-03-09 18:58:54 +00:00
}
2020-06-09 07:18:56 +00:00
readingsEndUpdate ( $ hash , 1 ) ;
2019-03-09 18:58:54 +00:00
2020-06-20 08:02:04 +00:00
my $ ldlv = $ stpl { liveData } { level } ;
2020-12-01 20:51:52 +00:00
# my $cclv = $stpl{consumerCurrentdata}{level};
2020-06-20 08:02:04 +00:00
my $ lddo = $ subs { $ name } { liveData } { doit } ;
my $ pv = ReadingsNum ( $ name , "${ldlv}_PV" , 0 ) ;
my $ fi = ReadingsNum ( $ name , "${ldlv}_FeedIn" , 0 ) ;
my $ gc = ReadingsNum ( $ name , "${ldlv}_GridConsumption" , 0 ) ;
my $ sum = $ fi - $ gc ;
2019-03-10 07:34:29 +00:00
2020-07-06 21:31:24 +00:00
my $ ts = strftime ( '%Y-%m-%d %H:%M:%S' , localtime ) ;
if ( AttrVal ( "global" , "language" , "EN" ) eq "DE" ) {
$ ts = strftime ( '%d.%m.%Y %H:%M:%S' , localtime ) ;
2019-03-10 07:34:29 +00:00
}
2019-03-03 20:08:04 +00:00
readingsBeginUpdate ( $ hash ) ;
2020-06-03 13:33:11 +00:00
2020-06-09 10:40:42 +00:00
if ( ! $ errstate ) {
2020-12-01 20:51:52 +00:00
# if($setp ne "none") {
# my ($d,$susyid,$op) = split(":",$setp);
# $op = ($op eq "auto") ? "off (automatic)" : $op;
# readingsBulkUpdate($hash, "${cclv}_${d}_Switch", $op) if($susyid == 191);
# }
2020-07-06 21:31:24 +00:00
readingsBulkUpdate ( $ hash , "lastCycleTime" , $ ctime ) if ( $ ctime > 0 ) ;
readingsBulkUpdate ( $ hash , "summary" , $ sum . " W" ) if ( $ subs { $ name } { liveData } { doit } ) ;
readingsBulkUpdate ( $ hash , "lastSuccessTime" , $ ts ) ;
2020-06-03 13:33:11 +00:00
}
2020-06-09 07:18:56 +00:00
2020-06-03 13:33:11 +00:00
readingsBulkUpdate ( $ hash , "state" , $ state ) ;
2020-05-31 22:12:59 +00:00
readingsEndUpdate ( $ hash , 1 ) ;
2019-03-03 20:08:04 +00:00
2020-06-21 05:44:26 +00:00
finalCleanup ( $ hash ) ;
2020-05-31 22:12:59 +00:00
2019-06-03 23:00:53 +00:00
SPGRefresh ( $ hash , 0 , 1 ) ;
return ;
2019-03-03 20:08:04 +00:00
}
################################################################
## Timeout BlockingCall
################################################################
2020-04-20 14:55:36 +00:00
sub ParseAborted {
2019-03-03 20:08:04 +00:00
my ( $ hash , $ cause ) = @ _ ;
my $ name = $ hash - > { NAME } ;
2020-06-20 11:18:13 +00:00
$ cause = $ cause // "Timeout >process terminated<" ;
2019-06-16 08:20:19 +00:00
Log3 ( $ name , 1 , "$name - BlockingCall $hash->{HELPER}{RUNNING_PID}{fn} pid:$hash->{HELPER}{RUNNING_PID}{pid} $cause" ) ;
2020-06-21 05:44:26 +00:00
readingsSingleUpdate ( $ hash , "state" , "broken: " . $ cause , 1 ) ;
finalCleanup ( $ hash ) ;
return ;
}
################################################################
## Final cleanup of an execution
################################################################
sub finalCleanup {
my $ hash = shift ;
2020-09-30 18:35:10 +00:00
my $ name = $ hash - > { NAME } ;
2019-03-03 20:08:04 +00:00
delete ( $ hash - > { HELPER } { RUNNING_PID } ) ;
2020-06-21 05:44:26 +00:00
2019-06-12 21:29:10 +00:00
$ hash - > { HELPER } { GETTER } = "all" ;
$ hash - > { HELPER } { SETTER } = "none" ;
2019-06-03 23:00:53 +00:00
2020-10-10 19:05:38 +00:00
if ( AttrVal ( $ name , "cookieDelete" , "auto" ) =~ /Run/x ) {
Log3 ( $ name , 3 , "$name - force delete cookie file" ) ;
delcookiefile ( $ hash ) ;
2020-09-30 18:35:10 +00:00
}
2020-05-30 20:51:54 +00:00
2019-06-03 23:00:53 +00:00
return ;
2019-03-03 20:08:04 +00:00
}
################################################################
2020-05-30 20:51:54 +00:00
# Cookie-Datei löschen
2019-03-03 20:08:04 +00:00
################################################################
2020-04-20 14:55:36 +00:00
sub delcookiefile {
2020-06-21 05:44:26 +00:00
my $ hash = shift ;
my $ source = shift ; ;
my $ name = $ hash - > { NAME } ;
my $ err = "" ;
2020-05-30 20:51:54 +00:00
my $ cookieLocation = AttrVal ( $ name , "cookieLocation" , "./log/" . $ name . "_cookie.txt" ) ;
my $ delfile = unlink ( $ cookieLocation ) or $ err = $! ;
2019-03-03 20:08:04 +00:00
if ( $ delfile ) {
2020-04-20 14:55:36 +00:00
Log3 $ name , 3 , "$name - Cookie file deleted: $cookieLocation" ;
2020-05-30 20:51:54 +00:00
}
2019-03-03 20:08:04 +00:00
2020-05-27 16:58:01 +00:00
return ( $ err ) ;
2019-03-03 20:08:04 +00:00
}
2020-06-09 07:18:56 +00:00
################################################################
## Auswertung Live Daten
################################################################
2020-06-11 20:30:49 +00:00
sub extractLiveData { ## no critic 'complexity'
2020-06-10 21:43:56 +00:00
my $ hash = shift ;
my $ daref = shift ;
my $ live = shift ;
2020-06-09 07:18:56 +00:00
my $ name = $ hash - > { NAME } ;
my $ val = "" ;
Log3 ( $ name , 4 , "$name - ##### extracting live data #### " ) ;
2020-06-09 13:37:45 +00:00
$ live = eval { decode_json ( $ live ) } or do { Log3 ( $ name , 2 , "$name - ERROR - can't decode JSON Data" ) ;
return ;
} ;
2020-06-20 11:18:13 +00:00
2020-06-09 13:37:45 +00:00
my ( $ errMsg , $ warnMsg , $ infoMsg ) = ( 0 , 0 , 0 ) ;
2020-06-20 08:02:04 +00:00
my $ lv = $ stpl { liveData } { level } ;
2020-06-20 11:18:13 +00:00
my $ lang = AttrVal ( "global" , "language" , "EN" ) ;
my % hm = ( # Header Messages sprachenabhängig
"DE" = > "Nachricht von SMA Sunny Portal erhalten:" ,
"EN" = > "Message got from SMA Sunny Portal:"
) ;
2020-06-09 13:37:45 +00:00
2020-06-09 07:18:56 +00:00
if ( ref $ live eq "HASH" ) {
2020-07-06 21:31:24 +00:00
push @$ daref , "${lv}_FeedIn:" . $ live - > { FeedIn } . " W" if ( defined $ live - > { FeedIn } ) ;
push @$ daref , "${lv}_GridConsumption:" . $ live - > { GridConsumption } . " W" if ( defined $ live - > { GridConsumption } ) ;
push @$ daref , "${lv}_PV:" . $ live - > { PV } . " W" if ( defined $ live - > { PV } ) ;
push @$ daref , "${lv}_AutarkyQuote:" . $ live - > { AutarkyQuote } . " %" if ( defined $ live - > { AutarkyQuote } ) ;
push @$ daref , "${lv}_SelfConsumption:" . $ live - > { SelfConsumption } . " W" if ( defined $ live - > { SelfConsumption } ) ;
push @$ daref , "${lv}_SelfConsumptionQuote:" . $ live - > { SelfConsumptionQuote } . " %" if ( defined $ live - > { SelfConsumptionQuote } ) ;
push @$ daref , "${lv}_SelfSupply:" . $ live - > { SelfSupply } . " W" if ( defined $ live - > { SelfSupply } ) ;
push @$ daref , "${lv}_TotalConsumption:" . $ live - > { TotalConsumption } . " W" if ( defined $ live - > { TotalConsumption } ) ;
2020-06-20 08:02:04 +00:00
push @$ daref , "${lv}_BatteryIn:" . $ live - > { BatteryIn } . " W" if ( defined $ live - > { BatteryIn } ) ;
push @$ daref , "${lv}_BatteryOut:" . $ live - > { BatteryOut } . " W" if ( defined $ live - > { BatteryOut } ) ;
push @$ daref , "${lv}_BatteryMode:" . $ live - > { BatteryMode } . "" if ( defined $ live - > { BatteryMode } ) ;
push @$ daref , "${lv}_BatteryStateOfHealth:" . $ live - > { BatteryStateOfHealth } . "" if ( defined $ live - > { BatteryStateOfHealth } ) ;
push @$ daref , "${lv}_BatteryChargeStatus:" . $ live - > { BatteryChargeStatus } . " %" if ( defined $ live - > { BatteryChargeStatus } ) ;
push @$ daref , "${lv}_DirectConsumption:" . $ live - > { DirectConsumption } . " W" if ( defined $ live - > { DirectConsumption } ) ;
push @$ daref , "${lv}_DirectConsumptionQuote:" . $ live - > { DirectConsumptionQuote } . " %" if ( defined $ live - > { DirectConsumptionQuote } ) ;
push @$ daref , "${lv}_ModuleTemperature:" . $ live - > { ModuleTemperature } . "" if ( defined $ live - > { ModuleTemperature } ) ;
push @$ daref , "${lv}_Insolation:" . $ live - > { Insolation } . "" if ( defined $ live - > { Insolation } ) ;
push @$ daref , "${lv}_WindSpeed:" . $ live - > { WindSpeed } . "" if ( defined $ live - > { WindSpeed } ) ;
push @$ daref , "${lv}_EnvironmentTemperature:" . $ live - > { EnvironmentTemperature } . "" if ( defined $ live - > { EnvironmentTemperature } ) ;
2020-06-09 07:18:56 +00:00
2020-09-29 20:56:15 +00:00
if ( $ live - > { OperationHealth } ) {
my $ o = "Ok: " . $ live - > { OperationHealth } { Ok } ;
my $ w = "Warning: " . $ live - > { OperationHealth } { Warning } ;
my $ e = "Error: " . $ live - > { OperationHealth } { Error } ;
my $ u = "Unknown: " . $ live - > { OperationHealth } { Unknown } ;
push @$ daref , "${lv}_OperationHealth: $o, $w, $e, $u" ;
}
2020-06-09 07:18:56 +00:00
if ( $ live - > { ErrorMessages } [ 0 ] ) {
my @ em ;
$ errMsg = 1 ;
for my $ a ( @ { $ live - > { ErrorMessages } } ) {
2020-06-20 11:18:13 +00:00
push @ em , encode ( "utf8" , $ a ) ;
2020-06-09 07:18:56 +00:00
}
$ val = join " " , @ em if ( @ em ) ;
2020-06-20 11:18:13 +00:00
push @$ daref , "${lv}_ErrorMessages:" . qq{ <html><b>$hm { $lang } </b><br>$val</html> } ;
2020-06-09 07:18:56 +00:00
}
if ( $ live - > { WarningMessages } [ 0 ] ) {
my @ wm ;
$ warnMsg = 1 ;
for my $ a ( @ { $ live - > { WarningMessages } } ) {
2020-06-20 11:18:13 +00:00
push @ wm , encode ( "utf8" , $ a ) ;
2020-06-09 07:18:56 +00:00
}
$ val = join " " , @ wm if ( @ wm ) ;
2020-06-20 20:06:06 +00:00
push @$ daref , "${lv}_WarningMessages:" . qq{ <html><b>$hm { $lang } </b><br>$val</html> } ;
2020-06-09 07:18:56 +00:00
}
if ( $ live - > { InfoMessages } [ 0 ] ) {
my @ im ;
$ infoMsg = 1 ;
for my $ a ( @ { $ live - > { InfoMessages } } ) {
2020-06-20 11:18:13 +00:00
push @ im , encode ( "utf8" , $ a ) ;
2020-06-09 07:18:56 +00:00
}
$ val = join " " , @ im if ( @ im ) ;
2020-06-20 20:06:06 +00:00
push @$ daref , "${lv}_InfoMessages:" . qq{ <html><b>$hm { $lang } </b><br>$val</html> } ;
2020-06-09 07:18:56 +00:00
}
}
2020-06-20 08:02:04 +00:00
BlockingInformParent ( "FHEM::SMAPortal::delReadingFromBlocking" , [ $ name , "${lv}_ErrorMessages" ] , 1 ) if ( ! $ errMsg ) ;
BlockingInformParent ( "FHEM::SMAPortal::delReadingFromBlocking" , [ $ name , "${lv}_WarningMessages" ] , 1 ) if ( ! $ warnMsg ) ;
BlockingInformParent ( "FHEM::SMAPortal::delReadingFromBlocking" , [ $ name , "${lv}_InfoMessages" ] , 1 ) if ( ! $ infoMsg ) ;
2020-06-09 07:18:56 +00:00
return ;
}
2019-03-03 20:08:04 +00:00
################################################################
## Auswertung Forecast Daten
################################################################
2020-06-04 19:19:45 +00:00
sub extractForecastData { ## no critic 'complexity'
2020-06-09 07:18:56 +00:00
my $ hash = shift ;
my $ daref = shift ;
my $ forecast = shift ;
my $ name = $ hash - > { NAME } ;
2019-03-03 20:08:04 +00:00
2019-06-16 08:20:19 +00:00
Log3 ( $ name , 4 , "$name - ##### extracting forecast data #### " ) ;
2020-06-09 13:37:45 +00:00
$ forecast = eval { decode_json ( $ forecast ) } or do { Log3 ( $ name , 2 , "$name - ERROR - can't decode JSON Data" ) ;
return ;
} ;
2020-06-20 08:02:04 +00:00
2019-03-03 20:08:04 +00:00
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" ;
2020-06-20 08:02:04 +00:00
my $ lv = $ stpl { forecastData } { level } ;
2019-03-03 20:08:04 +00:00
my $ PV_sum = 0 ;
my $ consum_sum = 0 ;
2020-06-09 10:40:42 +00:00
my $ sum = 0 ;
2019-03-03 20:08:04 +00:00
# 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
2019-03-24 15:51:33 +00:00
# Energie wird als "J" geliefert, Wh = J / 3600
2020-06-03 13:33:11 +00:00
for my $ fc_obj ( @ { $ forecast - > { 'ForecastSeries' } } ) {
2019-05-30 07:44:31 +00:00
my $ fc_datetime = $ fc_obj - > { 'TimeStamp' } - > { 'DateTime' } ; # Example for DateTime: 2016-02-15T23:00:00
my $ tkind = $ fc_obj - > { 'TimeStamp' } - > { 'Kind' } ; # Zeitart: Unspecified, Utc
2019-03-03 20:08:04 +00:00
# Calculate Unix timestamp (month begins at 0, year at 1900)
2020-04-20 14:55:36 +00:00
my ( $ fc_year , $ fc_month , $ fc_day , $ fc_hour ) = $ fc_datetime =~ /^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):00:00$/x ;
2019-03-03 20:08:04 +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 ) ;
2019-05-30 07:44:31 +00:00
# Use also old data to integrate daily PV and Consumption
if ( $ current_day == $ fc_day ) {
2020-04-21 11:37:45 +00:00
$ PV_sum += int ( $ fc_obj - > { 'PvMeanPower' } - > { 'Amount' } ) ; # integrator of daily PV in Wh
$ consum_sum += int ( $ fc_obj - > { 'ConsumptionForecast' } - > { 'Amount' } / 3600 ) ; # integrator of daily Consumption forecast in Wh
2019-05-30 07:44:31 +00:00
}
2019-03-03 20:08:04 +00:00
# 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 ) {
2020-04-21 11:37:45 +00:00
$ nextFewHoursSum { 'PV' } += $ fc_obj - > { 'PvMeanPower' } - > { 'Amount' } ; # Wh
$ nextFewHoursSum { 'Consumption' } += $ fc_obj - > { 'ConsumptionForecast' } - > { 'Amount' } / 3600 ; # Wh
$ nextFewHoursSum { 'Total' } += $ fc_obj - > { 'PvMeanPower' } - > { 'Amount' } - $ fc_obj - > { 'ConsumptionForecast' } - > { 'Amount' } / 3600 ; # Wh
$ nextFewHoursSum { 'ConsumpRcmd' } += $ fc_obj - > { 'IsConsumptionRecommended' } ? 1 : 0 ;
2019-03-03 20:08:04 +00:00
}
# If data is for the rest of the current day
if ( $ current_day == $ fc_day ) {
2020-04-21 11:37:45 +00:00
$ restOfDaySum { 'PV' } += $ fc_obj - > { 'PvMeanPower' } - > { 'Amount' } ; # Wh
$ restOfDaySum { 'Consumption' } += $ fc_obj - > { 'ConsumptionForecast' } - > { 'Amount' } / 3600 ; # Wh
$ restOfDaySum { 'Total' } += $ fc_obj - > { 'PvMeanPower' } - > { 'Amount' } - $ fc_obj - > { 'ConsumptionForecast' } - > { 'Amount' } / 3600 ; # Wh
$ restOfDaySum { 'ConsumpRcmd' } += $ fc_obj - > { 'IsConsumptionRecommended' } ? 1 : 0 ;
2019-05-30 07:44:31 +00:00
}
2019-03-03 20:08:04 +00:00
# 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 ) {
2020-04-21 11:37:45 +00:00
$ tomorrowSum { 'PV' } += $ fc_obj - > { 'PvMeanPower' } - > { 'Amount' } if ( exists ( $ fc_obj - > { 'PvMeanPower' } - > { 'Amount' } ) ) ; # Wh
$ tomorrowSum { 'Consumption' } += $ fc_obj - > { 'ConsumptionForecast' } - > { 'Amount' } / 3600 ; # Wh
$ tomorrowSum { 'Total' } += $ fc_obj - > { 'PvMeanPower' } - > { 'Amount' } - $ fc_obj - > { 'ConsumptionForecast' } - > { 'Amount' } / 3600 if ( $ fc_obj - > { 'PvMeanPower' } - > { 'Amount' } ) ; # Wh
$ tomorrowSum { 'ConsumpRcmd' } += $ fc_obj - > { 'IsConsumptionRecommended' } ? 1 : 0 ;
2019-03-03 20:08:04 +00:00
}
# Update values in Fhem if less than 24 hours in the future
2020-04-20 14:55:36 +00:00
# TimeStamp Kind: "Unspecified"
2020-06-20 08:02:04 +00:00
if ( $ obj_nr < 24 ) {
my $ time_str = "ThisHour" ;
$ time_str = "NextHour" . sprintf ( "%02d" , $ obj_nr ) if ( $ fc_diff_hours > 0 ) ;
if ( $ time_str =~ /NextHour/x ) {
push @$ daref , "${lv}_${time_str}_Time:" . TimeAdjust ( $ hash , $ fc_obj - > { 'TimeStamp' } - > { 'DateTime' } , $ tkind ) ;
push @$ daref , "${lv}_${time_str}_PvMeanPower:" . int ( $ fc_obj - > { 'PvMeanPower' } - > { 'Amount' } ) . " Wh" ; # in W als Durchschnitt geliefet, d.h. eine Stunde -> Wh
push @$ daref , "${lv}_${time_str}_Consumption:" . int ( $ fc_obj - > { 'ConsumptionForecast' } - > { 'Amount' } / 3600 ) . " Wh" ; # {'ConsumptionForecast'}->{'Amount'} wird als J = Ws geliefert
push @$ daref , "${lv}_${time_str}_IsConsumptionRecommended:" . ( $ fc_obj - > { 'IsConsumptionRecommended' } ? "yes" : "no" ) ;
push @$ daref , "${lv}_${time_str}_Total:" . ( int ( $ fc_obj - > { 'PvMeanPower' } - > { 'Amount' } ) - int ( $ fc_obj - > { 'ConsumptionForecast' } - > { 'Amount' } / 3600 ) ) . " Wh" ;
# add WeatherId Helper to show weather icon
BlockingInformParent ( "FHEM::SMAPortal::setFromBlocking" , [ $ name , "NULL" , "${lv}_" . $ { time_str } . "_WeatherId:" . int ( $ fc_obj - > { 'WeatherId' } ) ] , 1 ) if ( defined $ fc_obj - > { 'WeatherId' } ) ;
}
if ( $ time_str =~ /ThisHour/x ) {
push @$ daref , "${lv}_${time_str}_Time:" . TimeAdjust ( $ hash , $ fc_obj - > { 'TimeStamp' } - > { 'DateTime' } , $ tkind ) ;
push @$ daref , "${lv}_${time_str}_PvMeanPower:" . int ( $ fc_obj - > { 'PvMeanPower' } - > { 'Amount' } ) . " Wh" ; # in W als Durchschnitt geliefet, d.h. eine Stunde -> Wh
push @$ daref , "${lv}_${time_str}_Consumption:" . int ( $ fc_obj - > { 'ConsumptionForecast' } - > { 'Amount' } / 3600 ) . " Wh" ; # {'ConsumptionForecast'}->{'Amount'} wird als J = Ws geliefert
push @$ daref , "${lv}_${time_str}_IsConsumptionRecommended:" . ( $ fc_obj - > { 'IsConsumptionRecommended' } ? "yes" : "no" ) ;
push @$ daref , "${lv}_${time_str}_Total:" . ( int ( $ fc_obj - > { 'PvMeanPower' } - > { 'Amount' } ) - int ( $ fc_obj - > { 'ConsumptionForecast' } - > { 'Amount' } / 3600 ) ) . " Wh" ;
# add WeatherId Helper to show weather icon
BlockingInformParent ( "FHEM::SMAPortal::setFromBlocking" , [ $ name , "NULL" , "${lv}_" . $ { time_str } . "_WeatherId:" . int ( $ fc_obj - > { 'WeatherId' } ) ] , 1 ) if ( defined $ fc_obj - > { 'WeatherId' } ) ;
2019-03-03 20:08:04 +00:00
}
}
# Increment object counter
$ obj_nr + + ;
}
2020-06-20 08:02:04 +00:00
push @$ daref , "${lv}_Next04Hours_Consumption:" . int ( $ nextFewHoursSum { 'Consumption' } ) . " Wh" ;
push @$ daref , "${lv}_Next04Hours_PV:" . int ( $ nextFewHoursSum { 'PV' } ) . " Wh" ;
push @$ daref , "${lv}_Next04Hours_Total:" . int ( $ nextFewHoursSum { 'Total' } ) . " Wh" ;
push @$ daref , "${lv}_Next04Hours_IsConsumptionRecommended:" . int ( $ nextFewHoursSum { 'ConsumpRcmd' } ) . " h" ;
push @$ daref , "${lv}_ForecastToday_Consumption:" . $ consum_sum . " Wh" ;
push @$ daref , "${lv}_ForecastToday_PV:" . $ PV_sum . " Wh" ;
push @$ daref , "${lv}_RestOfDay_Consumption:" . int ( $ restOfDaySum { 'Consumption' } ) . " Wh" ;
push @$ daref , "${lv}_RestOfDay_PV:" . int ( $ restOfDaySum { 'PV' } ) . " Wh" ;
push @$ daref , "${lv}_RestOfDay_Total:" . int ( $ restOfDaySum { 'Total' } ) . " Wh" ;
push @$ daref , "${lv}_RestOfDay_IsConsumptionRecommended:" . int ( $ restOfDaySum { 'ConsumpRcmd' } ) . " h" ;
push @$ daref , "${lv}_Tomorrow_Consumption:" . int ( $ tomorrowSum { 'Consumption' } ) . " Wh" ;
push @$ daref , "${lv}_Tomorrow_PV:" . int ( $ tomorrowSum { 'PV' } ) . " Wh" ;
push @$ daref , "${lv}_Tomorrow_Total:" . int ( $ tomorrowSum { 'Total' } ) . " Wh" ;
push @$ daref , "${lv}_Tomorrow_IsConsumptionRecommended:" . int ( $ tomorrowSum { 'ConsumpRcmd' } ) . " h" ;
2020-06-04 19:19:45 +00:00
2019-03-03 20:08:04 +00:00
return ;
}
2019-03-09 18:58:54 +00:00
################################################################
## Auswertung Wetterdaten
################################################################
2020-04-20 14:55:36 +00:00
sub extractWeatherData {
2020-06-09 07:18:56 +00:00
my $ hash = shift ;
my $ daref = shift ;
my $ weather = shift ;
my $ name = $ hash - > { NAME } ;
2019-03-09 18:58:54 +00:00
2019-06-16 08:20:19 +00:00
Log3 ( $ name , 4 , "$name - ##### extracting weather data #### " ) ;
2020-06-09 13:37:45 +00:00
$ weather = eval { decode_json ( $ weather ) } or do { Log3 ( $ name , 2 , "$name - ERROR - can't decode JSON Data" ) ;
return ;
} ;
2020-06-20 08:02:04 +00:00
my $ lv = $ stpl { weatherData } { level } ;
2020-06-09 13:37:45 +00:00
2019-03-09 18:58:54 +00:00
for my $ k ( keys %$ weather ) {
2020-06-03 13:33:11 +00:00
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 } ) ;
2020-06-20 11:41:12 +00:00
my $ wdesc = encode ( "utf8" , $ weather - > { $ k } { WeatherDescription } ) ;
2020-06-09 07:18:56 +00:00
$ wdesc =~ s/t/T/x if ( $ wdesc =~ /^t/x ) ;
2020-06-03 13:33:11 +00:00
$ day =~ s/t/T/x ;
2020-06-20 08:02:04 +00:00
push @$ daref , "${lv}_${day}_Temperature:$temp $symbol" ;
push @$ daref , "${lv}_${day}_WeatherDescription:$wdesc" ;
2019-03-09 18:58:54 +00:00
}
2020-06-09 07:18:56 +00:00
}
2019-03-09 18:58:54 +00:00
return ;
}
2020-06-01 15:02:28 +00:00
################################################################
# Auswertung Statistic Daten
2020-08-08 05:16:54 +00:00
# $period = Day[_<date>] |
# Month[_<date>] |
# Year[_<date>] |
# Total
2020-06-01 15:02:28 +00:00
################################################################
sub extractStatisticData {
2020-12-01 20:51:52 +00:00
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ daref = $ paref - > { daref } ;
my $ data_cont = $ paref - > { data_cont } ; # empfangene Daten decoded Content
my $ data = $ paref - > { data } ; # empfangene Rohdaten
my $ period = $ paref - > { addon } ;
my $ tag = $ paref - > { tag } ;
2020-06-09 07:18:56 +00:00
my $ name = $ hash - > { NAME } ;
2020-06-01 15:02:28 +00:00
my $ sd ;
2020-10-11 19:25:54 +00:00
Log3 ( $ name , 4 , "$name - extracting balance data " ) ;
2020-06-01 15:02:28 +00:00
2020-12-01 20:51:52 +00:00
$ data_cont = eval { decode_json ( $ data_cont ) } or do { Log3 ( $ name , 2 , "$name - ERROR - can't decode JSON Data" ) ;
2020-06-09 13:37:45 +00:00
return ;
} ;
2020-06-27 10:15:45 +00:00
my $ lv = $ stpl { $ tag } { level } ;
2020-06-09 13:37:45 +00:00
2020-12-01 20:51:52 +00:00
if ( ref $ data_cont eq "HASH" ) {
$ sd = decode_json ( encode ( 'UTF-8' , $ data_cont - > { d } ) ) ;
2020-06-01 15:02:28 +00:00
}
if ( $ sd && ref $ sd eq "ARRAY" ) {
for my $ a ( @$ sd ) { # jedes ARRAY-Element ist ein HASH
my $ k = $ a - > { Key } ;
my $ v = $ a - > { Value } ;
2020-08-08 05:16:54 +00:00
push @$ daref , "${lv}_${period}_${k}:$v" if ( defined $ statkeys { $ k } ) ;
2020-06-01 15:02:28 +00:00
}
2020-06-09 07:18:56 +00:00
}
2020-06-01 15:02:28 +00:00
return ;
}
2019-03-03 20:08:04 +00:00
################################################################
2019-03-21 21:39:30 +00:00
## Auswertung Anlagendaten
################################################################
2020-06-20 11:18:13 +00:00
sub extractPlantMasterData {
2020-06-09 07:18:56 +00:00
my $ hash = shift ;
my $ daref = shift ;
my $ forecast = shift ;
2020-06-24 16:24:42 +00:00
my $ addon = shift ;
my $ data = shift ; # gelieferte Rohdaten
2020-12-01 20:51:52 +00:00
2020-06-09 07:18:56 +00:00
my $ name = $ hash - > { NAME } ;
2020-12-01 20:51:52 +00:00
2019-03-21 21:39:30 +00:00
my ( $ amount , $ unit ) ;
2020-06-20 11:18:13 +00:00
Log3 ( $ name , 4 , "$name - ##### extracting plant master data #### " ) ;
2019-06-16 08:20:19 +00:00
2020-06-09 13:37:45 +00:00
$ forecast = eval { decode_json ( $ forecast ) } or do { Log3 ( $ name , 2 , "$name - ERROR - can't decode JSON Data" ) ;
return ;
} ;
2020-06-20 11:18:13 +00:00
my $ lv = $ stpl { plantMasterData } { level } ;
2020-06-24 16:24:42 +00:00
my $ plantOid = $ forecast - > { 'ForecastTimeframes' } - > { 'PlantOid' } ; # Plant ID aus JSON filtern
if ( ! $ plantOid ) { # Plant ID aus Cookie Header extrhieren wenn nicht mi JSON geliefert (kommt vor)
Log3 ( $ name , 4 , "$name - Plant ID not set in data, get it from cookie ..." ) ;
my $ sc = $ data - > header ( 'Set-Cookie' ) // "" ;
( $ plantOid ) = $ sc =~ /plantOid=([0-9a-z-]*);/x ;
}
2020-06-20 08:02:04 +00:00
2020-06-09 10:40:42 +00:00
if ( $ plantOid ) { # wichtig für erweiterte Selektionen
Log3 ( $ name , 4 , "$name - Plant ID: " . $ plantOid ) ;
2020-06-10 15:12:41 +00:00
$ hash - > { HELPER } { PLANTOID } = $ plantOid ;
BlockingInformParent ( "FHEM::SMAPortal::setFromBlocking" , [ $ name , "NULL" , "PLANTOID:$plantOid" ] , 1 ) ;
2020-10-11 19:25:54 +00:00
}
else {
2020-06-09 10:40:42 +00:00
Log3 ( $ name , 4 , "$name - Plant ID not set !" ) ;
}
2019-03-21 21:39:30 +00:00
my $ ppp = $ forecast - > { 'PlantPeakPower' } ;
2020-06-03 13:33:11 +00:00
if ( $ ppp ) {
2019-03-21 21:39:30 +00:00
$ amount = $ forecast - > { 'PlantPeakPower' } { 'Amount' } ;
2020-06-04 19:19:45 +00:00
$ unit = $ forecast - > { 'PlantPeakPower' } { 'StandardUnit' } { 'Symbol' } ;
2020-06-20 08:02:04 +00:00
push @$ daref , "${lv}_PlantPeakPower:$amount $unit" ;
2020-06-04 19:19:45 +00:00
2020-06-20 11:18:13 +00:00
Log3 $ name , 4 , "$name - plantMasterData \"PlantPeakPower Amount\": $amount" ;
Log3 $ name , 4 , "$name - plantMasterData \"PlantPeakPower Symbol\": $unit" ;
2019-03-21 21:39:30 +00:00
}
return ;
}
2019-03-24 15:51:33 +00:00
################################################################
2020-06-20 08:02:04 +00:00
## Auswertung Anlagenlogbuch
################################################################
sub extractPlantLogbook {
my $ hash = shift ;
my $ daref = shift ;
my $ logdata = shift ;
my $ name = $ hash - > { NAME } ;
Log3 ( $ name , 4 , "$name - ##### extracting plant logbook data #### " ) ;
$ logdata = eval { decode_json ( $ logdata ) } or do { Log3 ( $ name , 2 , "$name - ERROR - can't decode JSON Data" ) ;
return ;
} ;
my $ lv = $ stpl { plantLogbook } { level } ;
my % colors = ( # Farben Highlighting
"Info" = > qq{ <span style="color: green;"> } ,
"Warning" = > qq{ <span style="color: orange;"> } ,
"Warnung" = > qq{ <span style="color: orange;"> } ,
"Disturbance" = > qq{ <span style="color: red;"> } ,
"Störung" = > qq{ <span style="color: red;"> } ,
"Error" = > qq{ <span style="color: red;"> } ,
"Fehler" = > qq{ <span style="color: red;"> } ,
"Unknown" = > qq{ <span style="color: black;"> } ,
) ;
my $ eh = "</span>" ; # Endestring Highlighting
if ( ref $ logdata - > { aaData } eq "ARRAY" ) {
my @ ld = @ { $ logdata - > { aaData } } ;
for my $ ae ( @ ld ) { # jedes ARRAY-Element ist ein HASH
my $ dn = encode ( "utf8" , $ ae - > { DeviceName } ) ;
my $ ts = $ ae - > { Timestamp } ;
my $ dc = encode ( "utf8" , $ ae - > { Description } ) ;
my $ id = $ ae - > { MessageId } ;
my ( $ mt ) = $ ae - > { MessageType } =~ /alt='(.*?)'/x ;
$ mt = encode ( "utf8" , $ mt ) // "Unknown" ;
my $ bh = $ colors { $ mt } ;
my $ v = qq{ <html><b>$bh $mt $eh</b><br>$dn : $ts <br>$dc</html> } ;
push @$ daref , "${lv}_LogbookEntry_${id}:$v" ;
}
}
return ;
}
################################################################
## Auswertung Consumer Plan Data (aus forecastData)
2019-03-24 15:51:33 +00:00
################################################################
2020-06-20 08:02:04 +00:00
sub extractConsumerPlanData {
2020-06-09 07:18:56 +00:00
my $ hash = shift ;
my $ daref = shift ;
my $ forecast = shift ;
2020-12-01 20:51:52 +00:00
2020-06-09 07:18:56 +00:00
my $ name = $ hash - > { NAME } ;
2019-03-24 15:51:33 +00:00
my % consumers ;
my ( $ key , $ val ) ;
2020-06-20 08:02:04 +00:00
Log3 ( $ name , 4 , "$name - ##### extracting consumer plan data #### " ) ;
2019-06-16 08:20:19 +00:00
2020-06-09 13:37:45 +00:00
$ forecast = eval { decode_json ( $ forecast ) } or do { Log3 ( $ name , 2 , "$name - ERROR - can't decode JSON Data" ) ;
return ;
} ;
2020-06-20 08:02:04 +00:00
my $ lv = $ stpl { forecastData } { level } ;
2019-03-24 15:51:33 +00:00
# Schleife über alle Consumer Objekte
my $ i = 0 ;
2020-06-03 13:33:11 +00:00
for my $ c ( @ { $ forecast - > { 'Consumers' } } ) {
2020-06-20 08:02:04 +00:00
$ consumers { "${i}_ConsumerName" } = $ c - > { 'ConsumerName' } ;
2019-03-24 15:51:33 +00:00
$ consumers { "${i}_ConsumerOid" } = $ c - > { 'ConsumerOid' } ;
$ i + + ;
}
if ( % consumers && $ forecast - > { 'ForecastTimeframes' } ) {
# es sind Vorhersagen zu geplanten Verbraucherschaltzeiten vorhanden
2019-05-30 07:44:31 +00:00
# TimeFrameStart/End Kind: "Utc"
2020-06-03 13:33:11 +00:00
for my $ c ( @ { $ forecast - > { 'ForecastTimeframes' } { 'PlannedTimeFrames' } } ) {
2019-05-30 07:44:31 +00:00
my $ tkind = $ c - > { 'TimeFrameStart' } - > { 'Kind' } ; # Zeitart: Unspecified, Utc
2019-03-24 15:51:33 +00:00
my $ deviceOid = $ c - > { 'DeviceOid' } ;
2019-05-30 07:44:31 +00:00
my $ timeFrameStart = TimeAdjust ( $ hash , $ c - > { 'TimeFrameStart' } { 'DateTime' } , $ tkind ) ; # wandele UTC
my $ timeFrameEnd = TimeAdjust ( $ hash , $ c - > { 'TimeFrameEnd' } { 'DateTime' } , $ tkind ) ; # wandele UTC
2019-03-24 15:51:33 +00:00
my $ tz = $ c - > { 'TimeFrameStart' } { 'Kind' } ;
2020-06-03 13:33:11 +00:00
for my $ k ( keys ( % consumers ) ) {
2019-03-24 15:51:33 +00:00
$ val = $ consumers { $ k } ;
2020-04-20 14:55:36 +00:00
if ( $ val eq $ deviceOid && $ k =~ /^(\d+)_.*$/x ) {
2019-03-24 15:51:33 +00:00
my $ lfn = $ 1 ;
$ consumers { "${lfn}_PlannedOpTimeStart" } = $ timeFrameStart ;
$ consumers { "${lfn}_PlannedOpTimeEnd" } = $ timeFrameEnd ;
}
}
}
}
if ( % consumers ) {
2020-06-03 13:33:11 +00:00
for my $ key ( keys ( % consumers ) ) {
2020-06-20 12:46:27 +00:00
Log3 $ name , 4 , "$name - Consumer data \"$key\": " . encode ( "utf8" , $ consumers { $ key } ) ;
2020-06-20 08:02:04 +00:00
if ( $ key =~ /ConsumerName/x && $ key =~ /^(\d+)_.*$/x ) {
2019-03-24 15:51:33 +00:00
my $ lfn = $ 1 ;
my $ cn = $ consumers { "${lfn}_ConsumerName" } ; # Verbrauchername
2019-08-22 13:36:50 +00:00
next if ( ! $ cn ) ;
2019-07-01 16:33:43 +00:00
$ cn = replaceJunkSigns ( $ cn ) ; # evtl. Umlaute/Leerzeichen im Verbrauchernamen ersetzen
2019-03-24 15:51:33 +00:00
my $ pos = $ consumers { "${lfn}_PlannedOpTimeStart" } ; # geplanter Start
my $ poe = $ consumers { "${lfn}_PlannedOpTimeEnd" } ; # geplantes Ende
2020-06-20 08:02:04 +00:00
my $ rb = "${lv}_${cn}_PlannedOpTimeBegin" ;
my $ re = "${lv}_${cn}_PlannedOpTimeEnd" ;
my $ rp = "${lv}_${cn}_Planned" ;
2020-10-11 19:25:54 +00:00
2020-06-04 19:19:45 +00:00
if ( $ pos ) {
2020-06-09 07:18:56 +00:00
push @$ daref , "$rb:$pos" ;
push @$ daref , "$rp:yes" ;
2020-10-11 19:25:54 +00:00
}
else {
2020-06-09 07:18:56 +00:00
push @$ daref , "$rb:undefined" ;
push @$ daref , "$rp:no" ;
2019-03-24 15:51:33 +00:00
}
2020-10-11 19:25:54 +00:00
2019-03-24 15:51:33 +00:00
if ( $ poe ) {
2020-06-09 07:18:56 +00:00
push @$ daref , "$re:$poe" ;
2020-10-11 19:25:54 +00:00
}
else {
2020-06-09 07:18:56 +00:00
push @$ daref , "$re:undefined" ;
2019-03-24 15:51:33 +00:00
}
}
}
}
return ;
}
2019-06-07 13:30:17 +00:00
################################################################
2020-06-20 08:02:04 +00:00
## Auswertung Consumer Stammdaten
2019-06-07 13:30:17 +00:00
################################################################
2020-06-20 08:02:04 +00:00
sub extractConsumerMasterdata {
2020-12-01 20:51:52 +00:00
my $ hash = shift ;
my $ daref = shift ;
my $ cdata = shift ;
2020-06-09 07:18:56 +00:00
my $ name = $ hash - > { NAME } ;
2019-06-07 13:30:17 +00:00
my % consumers ;
2020-11-05 19:48:33 +00:00
my % hcon ;
2020-06-10 15:12:41 +00:00
my ( $ i , $ res ) ;
2019-06-07 13:30:17 +00:00
2020-06-20 08:02:04 +00:00
Log3 ( $ name , 4 , "$name - ##### extracting consumer master data #### " ) ;
2019-06-16 08:20:19 +00:00
2020-12-01 20:51:52 +00:00
$ cdata = eval { decode_json ( $ cdata ) } or do { Log3 ( $ name , 4 , "$name - No JSON Data were delivered from SMA. Possibly you haven't any consumer integrated." ) ;
return ;
} ;
2020-06-20 08:02:04 +00:00
my $ lv = $ stpl { consumerMasterdata } { level } ;
2020-06-09 13:37:45 +00:00
2020-06-20 08:02:04 +00:00
# allen Consumer Objekten die ID zuordnen
2019-06-07 13:30:17 +00:00
$ i = 0 ;
2020-12-01 20:51:52 +00:00
for my $ c ( @ { $ cdata - > { 'MeasurementData' } } ) {
2020-06-20 08:02:04 +00:00
$ consumers { "${i}_ConsumerName" } = $ c - > { 'DeviceName' } ;
2019-06-07 13:30:17 +00:00
$ consumers { "${i}_ConsumerOid" } = $ c - > { 'Consume' } { 'ConsumerOid' } ;
2019-06-09 05:41:14 +00:00
$ consumers { "${i}_ConsumerLfd" } = $ i ;
2020-04-20 14:55:36 +00:00
my $ cn = $ consumers { "${i}_ConsumerName" } ; # Verbrauchername
2019-08-22 16:10:33 +00:00
next if ( ! $ cn ) ;
2020-11-17 16:48:00 +00:00
$ cn = replaceJunkSigns ( $ cn ) ; # Verbrauchername gemäß Readingreguarien anpassen
2019-06-09 05:31:57 +00:00
2020-11-05 19:48:33 +00:00
$ hcon { $ i } { DeviceName } = $ cn ;
2020-11-17 16:48:00 +00:00
$ hcon { $ i } { DeviceOrigName } = encode ( "utf8" , $ c - > { 'DeviceName' } ) ; # der originale Verbrauchername
2020-11-05 19:48:33 +00:00
$ hcon { $ i } { ConsumerOid } = $ consumers { "${i}_ConsumerOid" } ;
$ hcon { $ i } { SerialNumber } = $ c - > { 'SerialNumber' } ;
$ hcon { $ i } { SUSyID } = $ c - > { 'SUSyID' } ;
2020-06-20 08:02:04 +00:00
$ i + + ;
}
2020-11-05 19:48:33 +00:00
for my $ key ( keys % hcon ) {
for my $ parname ( keys % { $ hcon { $ key } } ) {
my $ val = $ hcon { $ key } { $ parname } ;
next if ( ! $ val ) ;
Log3 ( $ name , 4 , "$name - CONSUMER master data: $key -> $parname = $val" ) ;
BlockingInformParent ( "FHEM::SMAPortal::setFromBlocking" , [ $ name , "NULL" , "CONSUMER:$key:$parname:$val" ] , 1 ) ;
2020-06-20 08:02:04 +00:00
}
}
return ;
}
################################################################
## Auswertung Consumer Current Data
################################################################
sub extractConsumerCurrentdata {
my $ hash = shift ;
my $ daref = shift ;
my $ clivedata = shift ;
2020-12-01 20:51:52 +00:00
2020-06-20 08:02:04 +00:00
my $ name = $ hash - > { NAME } ;
my % consumers ;
my ( $ i , $ res ) ;
Log3 ( $ name , 4 , "$name - ##### extracting consumer current data #### " ) ;
$ clivedata = eval { decode_json ( $ clivedata ) } or do { Log3 ( $ name , 2 , "$name - ERROR - can't decode JSON Data" ) ;
return ;
} ;
my $ lv = $ stpl { consumerCurrentdata } { level } ;
# allen Consumer Objekten die ID zuordnen
$ i = 0 ;
for my $ c ( @ { $ clivedata - > { 'MeasurementData' } } ) {
$ consumers { "${i}_ConsumerName" } = $ c - > { 'DeviceName' } ;
$ consumers { "${i}_ConsumerOid" } = $ c - > { 'Consume' } { 'ConsumerOid' } ;
$ consumers { "${i}_ConsumerLfd" } = $ i ;
my $ cpower = $ c - > { 'Consume' } { 'Measurement' } ; # aktueller Energieverbrauch in W
my $ cn = $ consumers { "${i}_ConsumerName" } ; # Verbrauchername
next if ( ! $ cn ) ;
$ cn = replaceJunkSigns ( $ cn ) ;
2020-06-04 19:19:45 +00:00
2020-06-20 08:02:04 +00:00
push @$ daref , "${lv}_${cn}_Power:" . $ cpower . " W" if ( defined $ cpower ) ;
2020-04-20 14:55:36 +00:00
2019-06-07 13:30:17 +00:00
$ i + + ;
}
if ( % consumers && $ clivedata - > { 'ParameterData' } ) {
# es sind Daten zu den Verbrauchern vorhanden
# Kind: "Utc" ?
$ i = 0 ;
2020-06-03 13:33:11 +00:00
for my $ c ( @ { $ clivedata - > { 'ParameterData' } } ) {
2019-06-07 13:30:17 +00:00
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
my $ OperationAutoEna = $ c - > { 'Parameters' } [ 2 ] { 'Value' } ; # Automatic Betrieb erlaubt ?
2020-04-20 14:55:36 +00:00
my $ ltchange = TimeAdjust ( $ hash , $ c - > { 'Parameters' } [ 0 ] { 'Timestamp' } { 'DateTime' } , $ tkind ) ; # letzter Schaltzeitpunkt der Bluetooth-Steckdose (Verbraucher)
2020-04-21 11:37:45 +00:00
my $ cn = $ consumers { "${i}_ConsumerName" } ; # Verbrauchername
2019-08-22 16:10:33 +00:00
next if ( ! $ cn ) ;
2020-06-04 19:19:45 +00:00
$ cn = replaceJunkSigns ( $ cn ) ; # evtl. Umlaute/Leerzeichen im Verbrauchernamen ersetzen
2019-06-07 13:30:17 +00:00
if ( ! $ GriSwStt && $ GriSwAuto ) {
$ res = "off (automatic)" ;
2020-10-11 19:25:54 +00:00
}
elsif ( ! $ GriSwStt && ! $ GriSwAuto ) {
2019-06-07 13:30:17 +00:00
$ res = "off" ;
2020-10-11 19:25:54 +00:00
}
elsif ( $ GriSwStt ) {
2019-06-07 13:30:17 +00:00
$ res = "on" ;
2020-10-11 19:25:54 +00:00
}
else {
2019-06-07 13:30:17 +00:00
$ res = "undefined" ;
}
2020-06-20 08:02:04 +00:00
push @$ daref , "${lv}_${cn}_Switch:$res" ;
push @$ daref , "${lv}_${cn}_SwitchLastTime:$ltchange" ;
2019-06-07 13:30:17 +00:00
$ i + + ;
}
}
return ;
}
2019-06-10 16:32:58 +00:00
################################################################
## Auswertung Consumer History Energy Data
## $tf = Time Frame
################################################################
2020-04-20 14:55:36 +00:00
sub extractConsumerHistData { ## no critic 'complexity'
2020-06-09 07:18:56 +00:00
my $ hash = shift ;
my $ daref = shift ;
my $ chdata = shift ;
my $ tf = shift ;
2020-12-01 20:51:52 +00:00
2020-06-09 07:18:56 +00:00
my $ name = $ hash - > { NAME } ;
2019-06-10 16:32:58 +00:00
my % consumers ;
2020-06-09 07:18:56 +00:00
my ( $ i , $ gcr , $ gct , $ pcr , $ pct , $ tct , $ bcr , $ bct ) ;
2019-06-10 16:32:58 +00:00
2019-06-16 08:20:19 +00:00
Log3 ( $ name , 4 , "$name - ##### extracting consumer history data #### " ) ;
2020-06-09 13:37:45 +00:00
$ chdata = eval { decode_json ( $ chdata ) } or do { Log3 ( $ name , 2 , "$name - ERROR - can't decode JSON Data" ) ;
return ;
} ;
2020-06-20 08:02:04 +00:00
my $ livelvl = $ stpl { liveData } { level } ;
my $ bataval = ( defined ( ReadingsNum ( $ name , "${livelvl}_BatteryIn" , undef ) ) || defined ( ReadingsNum ( $ name , "${livelvl}_BatteryOut" , undef ) ) ) ? 1 : 0 ; # Identifikation ist Battery vorhanden ?
2020-06-09 13:37:45 +00:00
2020-06-20 08:02:04 +00:00
my $ dlvl = $ stpl { consumerDayData } { level } ;
my $ mlvl = $ stpl { consumerMonthData } { level } ;
my $ ylvl = $ stpl { consumerYearData } { level } ;
2019-06-10 16:32:58 +00:00
# allen Consumer Objekte die ID zuordnen
$ i = 0 ;
2020-06-03 13:33:11 +00:00
for my $ c ( @ { $ chdata - > { 'Consumers' } } ) {
2020-06-20 08:02:04 +00:00
$ consumers { "${i}_ConsumerName" } = $ c - > { 'DeviceName' } ;
2019-06-10 16:32:58 +00:00
$ consumers { "${i}_ConsumerOid" } = $ c - > { 'ConsumerOid' } ;
$ consumers { "${i}_ConsumerLfd" } = $ i ;
2020-04-20 14:55:36 +00:00
my $ cpower = $ c - > { 'TotalEnergy' } { 'Measurement' } ; # Energieverbrauch im Timeframe in Wh
my $ cn = $ consumers { "${i}_ConsumerName" } ; # Verbrauchername
2019-08-22 16:10:33 +00:00
next if ( ! $ cn ) ;
2019-07-01 16:33:43 +00:00
$ cn = replaceJunkSigns ( $ cn ) ;
2019-06-10 16:32:58 +00:00
2020-04-20 14:55:36 +00:00
if ( $ tf =~ /month|year/x ) {
2019-06-10 16:32:58 +00:00
$ tct = $ c - > { 'TotalEnergyMix' } { 'TotalConsumptionTotal' } ; # Gesamtverbrauch im Timeframe in Wh
$ gcr = $ c - > { 'TotalEnergyMix' } { 'GridConsumptionRelative' } ; # Anteil des Netzbezugs im Timeframe am Gesamtverbrauch in %
$ gct = $ c - > { 'TotalEnergyMix' } { 'GridConsumptionTotal' } ; # Anteil des Netzbezugs im Timeframe am Gesamtverbrauch in Wh
$ pcr = $ c - > { 'TotalEnergyMix' } { 'PvConsumptionRelative' } ; # Anteil des PV-Nutzung im Timeframe am Gesamtverbrauch in %
$ pct = $ c - > { 'TotalEnergyMix' } { 'PvConsumptionTotal' } ; # Anteil des PV-Nutzung im Timeframe am Gesamtverbrauch in Wh
2019-06-14 12:49:48 +00:00
$ bcr = $ c - > { 'TotalEnergyMix' } { 'BatteryConsumptionRelative' } ; # Anteil der Batterie-Nutzung im Timeframe am Gesamtverbrauch in %
$ bct = $ c - > { 'TotalEnergyMix' } { 'BatteryConsumptionTotal' } ; # Anteil der Batterie-Nutzung im Timeframe am Gesamtverbrauch in Wh
2019-06-10 16:32:58 +00:00
}
2020-06-20 08:02:04 +00:00
push @$ daref , "${dlvl}_${cn}_EnergyTotalDay:" . sprintf ( "%.0f" , $ cpower ) . " Wh" if ( defined ( $ cpower ) && $ tf eq "day" ) ;
push @$ daref , "${mlvl}_${cn}_EnergyTotalMonth:" . sprintf ( "%.0f" , $ cpower ) . " Wh" if ( defined ( $ cpower ) && $ tf eq "month" ) ;
push @$ daref , "${ylvl}_${cn}_EnergyTotalYear:" . sprintf ( "%.0f" , $ cpower ) . " Wh" if ( defined ( $ cpower ) && $ tf eq "year" ) ;
push @$ daref , "${mlvl}_${cn}_EnergyRelativeMonthGrid:" . sprintf ( "%.0f" , $ gcr ) . " %" if ( defined ( $ gcr ) && $ tf eq "month" ) ;
push @$ daref , "${mlvl}_${cn}_EnergyTotalMonthGrid:" . sprintf ( "%.0f" , $ gct ) . " Wh" if ( defined ( $ gct ) && $ tf eq "month" ) ;
push @$ daref , "${mlvl}_${cn}_EnergyRelativeMonthPV:" . sprintf ( "%.0f" , $ pcr ) . " %" if ( defined ( $ pcr ) && $ tf eq "month" ) ;
push @$ daref , "${mlvl}_${cn}_EnergyTotalMonthPV:" . sprintf ( "%.0f" , $ pct ) . " Wh" if ( defined ( $ pct ) && $ tf eq "month" ) ;
push @$ daref , "${mlvl}_${cn}_EnergyRelativeMonthBatt:" . sprintf ( "%.0f" , $ bcr ) . " %" if ( defined ( $ bcr ) && $ bataval && $ tf eq "month" ) ;
push @$ daref , "${mlvl}_${cn}_EnergyTotalMonthBatt:" . sprintf ( "%.0f" , $ bct ) . " Wh" if ( defined ( $ bct ) && $ bataval && $ tf eq "month" ) ;
push @$ daref , "${ylvl}_${cn}_EnergyRelativeYearGrid:" . sprintf ( "%.0f" , $ gcr ) . " %" if ( defined ( $ gcr ) && $ tf eq "year" ) ;
push @$ daref , "${ylvl}_${cn}_EnergyTotalYearGrid:" . sprintf ( "%.0f" , $ gct ) . " Wh" if ( defined ( $ gct ) && $ tf eq "year" ) ;
push @$ daref , "${ylvl}_${cn}_EnergyRelativeYearPV:" . sprintf ( "%.0f" , $ pcr ) . " %" if ( defined ( $ pcr ) && $ tf eq "year" ) ;
push @$ daref , "${ylvl}_${cn}_EnergyTotalYearPV:" . sprintf ( "%.0f" , $ pct ) . " Wh" if ( defined ( $ pct ) && $ tf eq "year" ) ;
push @$ daref , "${ylvl}_${cn}_EnergyRelativeYearBatt:" . sprintf ( "%.0f" , $ bcr ) . " %" if ( defined ( $ bcr ) && $ bataval && $ tf eq "year" ) ;
push @$ daref , "${ylvl}_${cn}_EnergyTotalYearBatt:" . sprintf ( "%.0f" , $ bct ) . " Wh" if ( defined ( $ bct ) && $ bataval && $ tf eq "year" ) ;
2019-06-10 16:32:58 +00:00
$ i + + ;
}
return ;
}
2020-12-01 20:51:52 +00:00
################################################################
# Auswertung Ergebnis aus Switch Consumer
################################################################
sub extractSwitchConsumerData {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ daref = $ paref - > { daref } ; # Referenz zum Datenarray
my $ data_cont = $ paref - > { data_cont } ; # Daten decoded Content
my $ data = $ paref - > { data } ; # empfangene Rohdaten
my $ addon = $ paref - > { addon } ; # ein optionales AddOn
my $ tag = $ paref - > { tag } ; # 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: $data_cont } ) ;
my $ state ;
if ( $ data_cont 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 ) ;
my $ cclv = $ stpl { consumerCurrentdata } { level } ;
$ op = ( $ op eq "auto" ) ? "off (automatic)" : $ op ;
push @$ daref , "${cclv}_${d}_Switch:$op" ;
}
else {
$ state = "ERROR - couldn't switch consumer $d to $op" ;
}
return $ state ;
}
################################################################
# Auswertung Ergebnis aus Manage Consumer
# By Energy
################################################################
sub extractManageConsumerByEnergy {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ ua = $ paref - > { ua } ; # LWP Useragent
my $ daref = $ paref - > { daref } ; # Referenz zum Datenarray
my $ data_cont = $ paref - > { data_cont } ; # Daten decoded Content
my $ data = $ paref - > { data } ; # empfangene Rohdaten
my $ addon = $ paref - > { addon } ; # ein optionales AddOn
my $ tag = $ paref - > { tag } ; # Kennzeichen der abgerufenen Daten/ der Abrufroutine
my $ name = $ hash - > { NAME } ;
my ( $ errstate , $ reread , $ retry ) = ( 0 , 0 , 0 ) ;
Log3 ( $ name , 4 , "$name - extracting manage Consumer by energy result " ) ;
my $ rescode = $ data - > code ; # HTML Code der Antwort (sollte 302 sein bei Erfolg)
my ( $ d , $ susyid , $ op ) = split ( ":" , $ addon ) ; # $op -> eingestellter Gridconsomption Schwellenwert
my $ pvlog = 100 - $ op ;
my $ state ;
if ( $ rescode == 302 ) {
my $ location = $ data - > header ( 'Location' ) ;
$ state = qq{ ok - Consumer "$d" set to condition: switch on if GridConsumption=$op% (PV=$pvlog%) is fulfilled } ;
Log3 ( $ name , 3 , qq{ $name - $state } ) ;
Log3 ( $ name , 3 , qq{ $name - next step: GET "$location" to read the new values are set } ) ;
BlockingInformParent ( "FHEM::SMAPortal::setFromBlocking" , [ $ name , "NULL" , "GETTER:all" ] , 1 ) ;
BlockingInformParent ( "FHEM::SMAPortal::setFromBlocking" , [ $ name , "NULL" , "SETTER:none" ] , 1 ) ;
for my $ key ( keys % { $ hash - > { HELPER } { CONSUMER } } ) {
my $ h = $ hash - > { HELPER } { CONSUMER } { $ key } { DeviceName } ;
if ( $ h && $ h eq $ d ) {
$ paref - > { oid } = $ hash - > { HELPER } { CONSUMER } { $ key } { ConsumerOid } ;
}
}
$ paref - > { name } = $ name ;
$ paref - > { state } = $ state ;
$ paref - > { addon } = "$d:noJSONdata" ; # das gemanagte Device in addon mitgeben
( $ errstate , $ state , $ reread , $ retry ) = _getConsumerEnergySetting ( $ paref ) ;
}
else {
$ state = qq{ ERROR - couldn't set Consumer "$d" set to condition: switch on if GridConsumption=$op% (PV=$pvlog%) } ;
}
return $ state ;
}
################################################################
# Abruf der aktuell eingestellten Energy Management Settings
# von Verbrauchern des Typs 315 (z.B. SMA EV Charger)
################################################################
sub extractConsumerEnergySetting {
my $ hash = shift ;
my $ daref = shift ;
my $ data_cont = shift ; # gelieferter data content
my $ addon = shift ;
my $ data = shift ; # gelieferte Rohdaten
my $ name = $ hash - > { NAME } ;
my ( $ errstate , $ reread , $ retry ) = ( 0 , 0 , 0 ) ;
Log3 ( $ name , 4 , "$name - extracting current Consumer energy settings " ) ;
my ( $ d ) = split ":" , $ addon ;
my $ cclv = $ stpl { consumerCurrentdata } { level } ;
my $ dcont = encode ( "utf8" , $ data - > decoded_content ) ;
my ( $ gcval ) = $ dcont =~ /var\sgridConsumptionValue\s=\s(.*?);/x ;
my ( $ pvval ) = $ dcont =~ /var\spvValue\s=\s(.*?);/x ;
$ gcval = sprintf ( "%.2f" , $ gcval ) * 100 ;
$ pvval = sprintf ( "%.2f" , $ pvval ) * 100 ;
push @$ daref , "${cclv}_${d}_SwitchCondition:GridConsumption=$gcval% PV=$pvval%" ;
return ;
}
2020-11-03 18:13:28 +00:00
################################################################
# Auswertung Daten aus Hilfsroutinen
################################################################
sub extractHelperData {
2020-12-01 20:51:52 +00:00
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ daref = $ paref - > { daref } ; # Referenz zum Datenarray
my $ data_cont = $ paref - > { data_cont } ; # Daten decoded Content
my $ data = $ paref - > { data } ; # empfangene Rohdaten
my $ addon = $ paref - > { addon } ; # ein optionales AddOn
my $ tag = $ paref - > { tag } ; # Kennzeichen der abgerufenen Daten/ der Abrufroutine
2020-11-03 18:13:28 +00:00
my $ name = $ hash - > { NAME } ;
my $ sd ;
Log3 ( $ name , 4 , "$name - extracting Helper data " ) ;
2020-12-01 20:51:52 +00:00
my $ decd = eval { decode_json ( $ data_cont ) } or do { Log3 ( $ name , 2 , "$name - ERROR - can't decode JSON Data" ) ;
return ;
} ;
2020-11-03 18:13:28 +00:00
2020-12-01 20:51:52 +00:00
if ( ref $ decd eq "HASH" ) {
while ( my ( $ k , $ v ) = each %$ decd ) {
2020-11-03 18:13:28 +00:00
push @$ daref , "$tag:$v" ;
}
}
return ;
}
2019-03-21 21:39:30 +00:00
################################################################
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>
################################################################
2020-04-20 14:55:36 +00:00
sub sortVersionNum {
2019-03-03 20:08:04 +00:00
my ( $ sseq , @ versions ) = @ _ ;
my @ sorted = map { $ _ - > [ 0 ] }
2020-04-20 14:55:36 +00:00
sort { $ a - > [ 1 ] cmp $ b - > [ 1 ] }
map { [ $ _ , pack "C*" , split /\./x ] } @ versions ;
2019-03-03 20:08:04 +00:00
@ sorted = map { join "." , unpack "C*" , $ _ }
sort
2020-04-20 14:55:36 +00:00
map { pack "C*" , split /\./x } @ versions ;
2019-03-03 20:08:04 +00:00
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
################################################################
2020-04-20 14:55:36 +00:00
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-25 19:19:05 +00:00
if ( $ modules { $ type } { META } { x_prereqs_src } && ! $ hash - > { HELPER } { MODMETAABSENT } ) {
2020-04-20 14:55:36 +00:00
# META-Daten sind vorhanden
$ modules { $ type } { META } { version } = "v" . $ v ; # Version aus META.json überschreiben, Anzeige mit {Dumper $modules{SMAPortal}{META}}
2020-12-02 09:37:43 +00:00
if ( $ modules { $ type } { META } { x_version } ) { # {x_version} ( nur gesetzt wenn $Id: 76_SMAPortal.pm 23272 2020-12-01 20:51:52Z DS_Starter $ im Kopf komplett! vorhanden )
2020-04-20 14:55:36 +00:00
$ modules { $ type } { META } { x_version } =~ s/1\.1\.1/$v/gx ;
2020-10-11 19:25:54 +00:00
}
else {
2020-04-20 14:55:36 +00:00
$ modules { $ type } { META } { x_version } = $ v ;
}
2020-12-02 09:37:43 +00:00
return $@ unless ( FHEM::Meta:: SetInternals ( $ hash ) ) ; # FVERSION wird gesetzt ( nur gesetzt wenn $Id: 76_SMAPortal.pm 23272 2020-12-01 20:51:52Z DS_Starter $ im Kopf komplett! vorhanden )
2020-04-20 14:55:36 +00:00
if ( __PACKAGE__ eq "FHEM::$type" || __PACKAGE__ eq $ type ) {
# es wird mit Packages gearbeitet -> Perl übliche Modulversion setzen
# mit {<Modul>->VERSION()} im FHEMWEB kann Modulversion abgefragt werden
use version 0.77 ; our $ VERSION = FHEM::Meta:: Get ( $ hash , 'version' ) ; ## no critic 'VERSION'
2019-03-16 06:48:59 +00:00
}
2020-10-11 19:25:54 +00:00
}
else {
2020-04-20 14:55:36 +00:00
# herkömmliche Modulstruktur
$ hash - > { VERSION } = $ v ;
2019-03-16 06:48:59 +00:00
}
return ;
}
2019-03-03 20:08:04 +00:00
################################################################
2020-06-20 12:32:35 +00:00
# delete Readings und Hash HELPER-Daten
2020-08-18 19:27:12 +00:00
# $conspl = providerLevel berücksichtigen
2019-03-03 20:08:04 +00:00
################################################################
2020-06-20 12:32:35 +00:00
sub deleteData {
2020-06-20 08:02:04 +00:00
my $ hash = shift ;
my $ conspl = shift ;
2019-03-03 20:08:04 +00:00
my $ name = $ hash - > { NAME } ;
my @ allrds = keys % { $ defs { $ name } { READINGS } } ;
2020-11-11 16:34:09 +00:00
my $ bl = "state|lastCycleTime|Counter|loginState|usedUserAgent" ; # Blacklist
2020-06-20 12:32:35 +00:00
2020-10-11 07:40:33 +00:00
my $ pblvl = $ stpl { plantLogbook } { level } ; # Logbuch Level
my $ ballvl = $ stpl { balanceDayData } { level } . "|" . # Level von balanceDayData, balanceMonthData, balanceYearData
$ stpl { balanceMonthData } { level } . "|" .
$ stpl { balanceYearData } { level } ;
2020-06-20 13:12:23 +00:00
2020-08-18 19:27:12 +00:00
if ( ! $ subs { $ name } { forecastData } { doit } ) { # wenn forecastData nicht abgerufen werden sollen -> Wetterdaten im HELPER löschen
2020-06-20 12:32:35 +00:00
my $ fclvl = $ stpl { forecastData } { level } ;
delete $ hash - > { HELPER } { "${fclvl}_ThisHour_WeatherId" } ;
for my $ i ( 1 .. 23 ) {
$ i = sprintf ( "%02d" , $ i ) ;
delete $ hash - > { HELPER } { "${fclvl}_NextHour${i}_WeatherId" } ;
}
}
2020-06-20 08:02:04 +00:00
2020-08-18 19:27:12 +00:00
if ( $ conspl ) { # Readings löschen wenn nicht im providerLevel enthalten
my $ pbl = q{ } ;
for my $ prl ( keys % { $ mandatory { $ name } } ) { # mandatory Provider die abgerufen wurden
my $ lvlm = $ mandatory { $ name } { $ prl } { level } ; # Forum: https://forum.fhem.de/index.php/topic,102112.msg1078990.html#msg1078990
if ( $ lvlm ) {
$ pbl . = "|^" . $ lvlm . "_" ;
}
}
for my $ prl ( keys % { $ subs { $ name } } ) { # Provider die abgerufen wurden
2020-09-30 18:35:10 +00:00
my $ lvl ;
if ( $ subs { $ name } { $ prl } { doit } ) {
$ lvl = $ subs { $ name } { $ prl } { level } ; # Forum: https://forum.fhem.de/index.php/topic,102112.msg1078990.html#msg1078990
}
2020-10-10 19:05:38 +00:00
2020-08-18 19:27:12 +00:00
if ( $ lvl ) {
$ pbl . = "|^" . $ lvl . "_" ;
2020-06-20 13:12:23 +00:00
}
2020-08-18 19:27:12 +00:00
}
$ bl . = $ pbl ; # Blacklist ergänzen
for my $ key ( @ allrds ) {
2020-10-10 19:05:38 +00:00
delete $ defs { $ name } { READINGS } { $ key } if ( $ key !~ /$bl/x ) ;
delete $ defs { $ name } { READINGS } { $ key } if ( $ key =~ /^$pblvl/x ) ; # Logbuchreadings immer löschen
2020-10-11 07:40:33 +00:00
delete $ defs { $ name } { READINGS } { $ key } if ( $ key =~ /^$ballvl/x ) ; # balance(Day|Month)Data Readings immer löschen wegen möglicher Relativverschiebung
2019-03-03 20:08:04 +00:00
}
2020-08-18 19:27:12 +00:00
2019-03-03 20:08:04 +00:00
return ;
}
2020-08-18 19:27:12 +00:00
for my $ key ( @ allrds ) { # alle Readings löschen bis auf Standard-Blacklist
2020-06-20 08:02:04 +00:00
delete ( $ defs { $ name } { READINGS } { $ key } ) if ( $ key !~ /$bl/x ) ;
2019-03-03 20:08:04 +00:00
}
return ;
}
2020-10-11 19:25:54 +00:00
################################################################
# erstelle addon als relative oder reale Datumangabe
################################################################
sub createDateAddon {
my $ paref = shift ;
my $ name = $ paref - > { name } ;
my $ bal = $ paref - > { bal } ;
my $ tag = $ paref - > { tag } ;
my $ daref = $ paref - > { daref } ;
my $ addon = $ paref - > { addon } ;
my $ addon1 = $ paref - > { addon1 } ;
if ( AttrVal ( $ name , "useRelativeNames" , 0 ) ) { # current-x verwenden statt effektives Datum
$ addon . = $ bal ;
my $ lv = $ stpl { $ tag } { level } ;
push @$ daref , "${lv}_${addon}_Date:$addon1" ;
}
else {
$ addon . = $ addon1 ;
}
return $ addon ;
}
2019-06-12 21:29:10 +00:00
################################################################
2020-05-28 06:52:47 +00:00
# statistische Counter managen
# $name = Name Device
# $rd = Name des Zählerreadings
################################################################
sub handleCounter {
2020-05-31 22:12:59 +00:00
my $ name = shift ;
my $ rd = shift ;
2020-05-28 06:52:47 +00:00
my $ cstring = ReadingsVal ( $ name , $ rd , "" ) ;
my ( $ day , $ count ) = split ( ":" , $ cstring ) ;
my $ mday = ( localtime ( time ) ) [ 3 ] ;
if ( ! $ day || $ day != $ mday ) {
$ count = 0 ;
$ day = $ mday ;
2020-06-01 15:02:28 +00:00
Log3 ( $ name , 2 , qq{ $name - reset counter "$rd" to >0< } ) if ( ! $ defs { $ name } - > { HELPER } { $ rd } ) ;
$ defs { $ name } - > { HELPER } { $ rd } = 1 ; # nur im fork setzen um doppelten Logeintrag zu vermeiden
2020-05-28 06:52:47 +00:00
}
$ count + + ;
$ cstring = "$rd:$day:$count" ;
2020-06-10 15:12:41 +00:00
BlockingInformParent ( "FHEM::SMAPortal::setFromBlocking" , [ $ name , $ cstring , "NULL" ] , 1 ) ;
2020-05-28 06:52:47 +00:00
return ;
}
2020-06-10 15:12:41 +00:00
###################################################################
2020-05-28 06:52:47 +00:00
# Werte aus BlockingCall heraus setzen
2020-05-31 08:53:05 +00:00
# Erwartete Liste:
2020-06-10 15:12:41 +00:00
# @setl = $name,$setread,$retries,$helper
###################################################################
2020-04-20 14:55:36 +00:00
sub setFromBlocking {
2020-11-05 19:48:33 +00:00
my $ name = shift ;
my $ setread = shift // "NULL" ;
my $ helper = shift // "NULL" ;
my $ hash = $ defs { $ name } ;
2020-05-26 15:31:05 +00:00
2020-05-31 08:53:05 +00:00
if ( $ setread ne "NULL" ) {
my @ cparts = split ":" , $ setread , 2 ;
2020-05-28 06:52:47 +00:00
readingsSingleUpdate ( $ hash , $ cparts [ 0 ] , $ cparts [ 1 ] , 1 ) ;
}
2020-06-10 15:12:41 +00:00
if ( $ helper ne "NULL" ) {
my ( $ hnam , $ k1 , $ k2 , $ k3 ) = split ":" , $ helper , 4 ;
2020-10-11 19:25:54 +00:00
2020-06-10 15:12:41 +00:00
if ( defined $ k3 ) {
$ hash - > { HELPER } { "$hnam" } { "$k1" } { "$k2" } = $ k3 ;
2020-10-11 19:25:54 +00:00
}
elsif ( defined $ k2 ) {
2020-06-10 15:12:41 +00:00
$ hash - > { HELPER } { "$hnam" } { "$k1" } = $ k2 ;
2020-10-11 19:25:54 +00:00
}
else {
2020-06-10 15:12:41 +00:00
$ hash - > { HELPER } { "$hnam" } = $ k1 ;
}
}
2019-06-12 21:29:10 +00:00
2020-05-30 20:51:54 +00:00
return 1 ;
2019-06-12 21:29:10 +00:00
}
2020-06-09 07:18:56 +00:00
################################################################
# Reading aus BlockingCall heraus löschen
# Erwartete Liste:
# @params = $name,$reading
################################################################
sub delReadingFromBlocking {
my $ name = shift ;
my $ reading = shift ;
my $ hash = $ defs { $ name } ;
readingsDelete ( $ hash , $ reading ) ;
return 1 ;
}
2020-10-31 16:17:33 +00:00
################################################################
# errechnet wieviel Tage ein gegebener Monat eines
# bestimmten Jahres hat
# $m: realer Monat (1..12)
# $y: reales Jahr (2020)
################################################################
sub daysInMonth {
my $ m = shift ;
my $ y = shift ;
my $ dim = $ m - 2 ? 30 + ( $ m * 3 % 7 < 4 ) : 28 + ! ( $ y % 4 || $ y % 400 * ! ( $ y % 100 ) ) ;
return $ dim ;
}
2019-03-24 15:51:33 +00:00
################################################################
2019-05-30 07:44:31 +00:00
# Timestamp korrigieren
2019-03-24 15:51:33 +00:00
################################################################
2020-04-20 14:55:36 +00:00
sub TimeAdjust {
2019-05-30 07:44:31 +00:00
my ( $ hash , $ t , $ tkind ) = @ _ ;
2020-04-20 14:55:36 +00:00
$ t =~ s/T/ /x ;
my ( $ datehour , $ rest ) = split ( /:/x , $ t , 2 ) ;
my ( $ year , $ month , $ day , $ hour ) = $ datehour =~ /(\d+)-(\d\d)-(\d\d)\s+(\d\d)/x ;
2019-03-24 15:51:33 +00:00
2019-05-30 07:44:31 +00:00
# Time::timegm - a UTC version of mktime()
2019-03-24 15:51:33 +00:00
# proto: $time = timegm($sec,$min,$hour,$mday,$mon,$year);
2019-05-30 07:44:31 +00:00
my $ epoch = timegm ( 0 , 0 , $ hour , $ day , $ month - 1 , $ year ) ;
2019-03-24 15:51:33 +00:00
# proto: ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
2020-04-20 14:55:36 +00:00
my $ isdst = ( localtime ( $ epoch ) ) [ 8 ] ;
2019-05-01 14:30:09 +00:00
2020-04-20 14:55:36 +00:00
if ( lc ( $ tkind ) =~ /unspecified/x ) {
2019-05-30 07:44:31 +00:00
if ( $ isdst ) {
$ epoch = $ epoch - 7200 ;
2020-10-11 19:25:54 +00:00
}
else {
2019-05-30 07:44:31 +00:00
$ epoch = $ epoch - 3600 ;
}
2019-05-01 14:30:09 +00:00
}
2019-05-30 07:44:31 +00:00
my ( $ lyear , $ lmonth , $ lday , $ lhour ) = ( localtime ( $ epoch ) ) [ 5 , 4 , 3 , 2 ] ;
2019-03-24 15:51:33 +00:00
$ lyear += 1900 ; # year is 1900 based
$ lmonth + + ; # month number is zero based
2019-05-01 14:30:09 +00:00
2019-03-24 15:51:33 +00:00
if ( AttrVal ( "global" , "language" , "EN" ) eq "DE" ) {
2020-04-20 14:55:36 +00:00
return ( sprintf ( "%02d.%02d.%04d %02d:%s" , $ lday , $ lmonth , $ lyear , $ lhour , $ rest ) ) ;
2020-10-11 19:25:54 +00:00
}
else {
2020-04-20 14:55:36 +00:00
return ( sprintf ( "%04d-%02d-%02d %02d:%s" , $ lyear , $ lmonth , $ lday , $ lhour , $ rest ) ) ;
2019-03-24 15:51:33 +00:00
}
}
2019-03-25 22:41:54 +00:00
###############################################################################
2019-07-01 16:33:43 +00:00
# Umlaute und ungültige Zeichen für Readingerstellung ersetzen
2019-03-25 22:41:54 +00:00
###############################################################################
2020-04-20 14:55:36 +00:00
sub replaceJunkSigns {
2019-07-01 16:33:43 +00:00
my ( $ rn ) = @ _ ;
2020-04-20 14:55:36 +00:00
$ rn =~ s/ß/ss/gx ;
$ rn =~ s/ä/ae/gx ;
$ rn =~ s/ö/oe/gx ;
$ rn =~ s/ü/ue/gx ;
$ rn =~ s/Ä/Ae/gx ;
$ rn =~ s/Ö/Oe/gx ;
$ rn =~ s/Ü/Ue/gx ;
2019-07-01 16:33:43 +00:00
$ rn = makeReadingName ( $ rn ) ;
return ( $ rn ) ;
2019-03-25 22:41:54 +00:00
}
2020-12-01 20:51:52 +00:00
###############################################################################
# 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 ;
}
2019-04-29 22:07:15 +00:00
###############################################################################
2019-05-30 07:44:31 +00:00
# Subroutine für Portalgrafik
2019-04-29 22:07:15 +00:00
###############################################################################
2020-04-20 14:55:36 +00:00
sub PortalAsHtml { ## no critic 'complexity'
2019-06-26 21:43:03 +00:00
my ( $ name , $ wlname , $ ftui ) = @ _ ;
my $ hash = $ defs { $ name } ;
my $ ret = "" ;
2019-05-01 14:30:09 +00:00
2020-04-20 14:55:36 +00:00
my ( $ icon , $ colorv , $ colorc , $ maxhours , $ hourstyle , $ header , $ legend , $ legend_txt , $ legend_style ) ;
2019-05-30 07:44:31 +00:00
my ( $ val , $ height , $ fsize , $ html_start , $ html_end , $ wlalias , $ weather , $ colorw , $ maxVal , $ show_night , $ type , $ kw ) ;
2019-07-07 18:54:56 +00:00
my ( $ maxDif , $ minDif , $ maxCon , $ v , $ z2 , $ z3 , $ z4 , $ show_diff , $ width , $ w , $ hdrDetail , $ hdrAlign ) ;
2019-05-30 07:44:31 +00:00
my $ he ; # Balkenhöhe
my ( % pv , % is , % t , % we , % di , % co ) ;
my @ pgCDev ;
2019-06-03 23:00:53 +00:00
# Kontext des aufrufenden SMAPortalSPG-Devices speichern für Refresh
2019-07-07 18:54:56 +00:00
$ hash - > { HELPER } { SPGDEV } = $ wlname ; # Name des aufrufenden SMAPortalSPG-Devices
2020-06-03 19:30:51 +00:00
$ hash - > { HELPER } { SPGROOM } = $ FW_room ? $ FW_room : "" ; # Raum aus dem das SMAPortalSPG-Device die Funktion aufrief
$ hash - > { HELPER } { SPGDETAIL } = $ FW_detail ? $ FW_detail : "" ; # Name des SMAPortalSPG-Devices (wenn Detailansicht)
2019-06-03 23:00:53 +00:00
2020-06-20 08:02:04 +00:00
my $ fdo = $ subs { $ name } { forecastData } { doit } ;
my $ fmin = $ subs { $ name } { forecastData } { level } ; # LXX Level
my $ fmaj = $ subs { $ name } { forecastData } { level } ;
my $ ldlv = $ subs { $ name } { liveData } { level } ;
my $ cclv = $ subs { $ name } { consumerCurrentdata } { level } ;
my ( $ pv0 , $ pv1 ) ;
$ pv0 = ReadingsNum ( $ name , "${fmin}_ThisHour_PvMeanPower" , undef ) if ( $ fmin ) ;
$ pv1 = ReadingsNum ( $ name , "${fmaj}_NextHour01_PvMeanPower" , undef ) if ( $ fmaj ) ;
2020-04-21 11:37:45 +00:00
2020-06-20 08:02:04 +00:00
if ( ! $ hash || ! defined ( $ defs { $ wlname } ) || ! $ fdo || ! defined $ pv0 || ! defined $ pv1 ) {
2020-04-21 11:37:45 +00:00
$ height = AttrNum ( $ wlname , 'beamHeight' , 200 ) ;
$ ret . = "<table class='roomoverview'>" ;
$ ret . = "<tr style='height:" . $ height . "px'>" ;
$ ret . = "<td>" ;
2020-06-21 05:44:26 +00:00
if ( ! $ hash ) { ## no critic "Cascading"
2019-06-03 23:00:53 +00:00
$ ret . = "Device \"$name\" doesn't exist !" ;
2020-10-11 19:25:54 +00:00
}
elsif ( ! defined ( $ defs { $ wlname } ) ) {
2019-06-03 23:00:53 +00:00
$ ret . = "Graphic device \"$wlname\" doesn't exist !" ;
2020-10-11 19:25:54 +00:00
}
elsif ( ! $ fdo ) {
2020-06-20 08:02:04 +00:00
$ ret . = qq{ The attribute "providerLevel" of device "$name" must contain the level "forecastData" and data must be retrieved ! } ;
2020-10-11 19:25:54 +00:00
}
elsif ( ! defined $ pv0 ) {
2020-06-20 08:02:04 +00:00
$ ret . = "Awaiting minor level forecast data ..." ;
2020-10-11 19:25:54 +00:00
}
elsif ( ! defined $ pv1 ) {
2020-07-02 19:45:08 +00:00
$ ret . = "Awaiting major level forecast data ..." ;
2019-06-03 23:00:53 +00:00
}
2020-04-21 11:37:45 +00:00
$ ret . = "</td>" ;
$ ret . = "</tr>" ;
$ ret . = "</table>" ;
2019-06-03 23:00:53 +00:00
return $ ret ;
}
2019-05-30 07:44:31 +00:00
2020-06-03 19:30:51 +00:00
@ pgCDev = split ( ',' , AttrVal ( $ wlname , "consumerList" , "" ) ) ; # definierte Verbraucher ermitteln
2019-06-03 23:00:53 +00:00
( $ legend_style , $ legend ) = split ( '_' , AttrVal ( $ wlname , 'consumerLegend' , 'icon_top' ) ) ;
2019-05-30 07:44:31 +00:00
$ legend = '' if ( ( $ legend_style eq 'none' ) || ( ! int ( @ pgCDev ) ) ) ;
2019-06-12 21:29:10 +00:00
# Verbraucherlegende und Steuerung
2019-05-30 07:44:31 +00:00
if ( $ legend ) {
2020-06-03 13:33:11 +00:00
for ( @ pgCDev ) {
2019-07-07 18:54:56 +00:00
my ( $ txt , $ im ) = split ( ':' , $ _ ) ; # $txt ist der Verbrauchername
2020-04-20 14:55:36 +00:00
my $ cmdon = "\"FW_cmd('$FW_ME$FW_subdir?XHR=1&cmd=set $name $txt on')\"" ;
2019-06-12 21:29:10 +00:00
my $ cmdoff = "\"FW_cmd('$FW_ME$FW_subdir?XHR=1&cmd=set $name $txt off')\"" ;
my $ cmdauto = "\"FW_cmd('$FW_ME$FW_subdir?XHR=1&cmd=set $name $txt auto')\"" ;
2019-06-26 21:43:03 +00:00
if ( $ ftui && $ ftui eq "ftui" ) {
$ cmdon = "\"ftui.setFhemStatus('set $name $txt on')\"" ;
$ cmdoff = "\"ftui.setFhemStatus('set $name $txt off')\"" ;
$ cmdauto = "\"ftui.setFhemStatus('set $name $txt auto')\"" ;
}
2020-06-20 08:02:04 +00:00
my $ swstate = ReadingsVal ( $ name , "${cclv}_" . $ txt . "_Switch" , "undef" ) ;
2020-04-20 14:55:36 +00:00
my $ swicon = "<img src=\"$FW_ME/www/images/default/1px-spacer.png\">" ;
if ( $ swstate eq "off" ) {
$ swicon = "<a onClick=$cmdon><img src=\"$FW_ME/www/images/default/10px-kreis-rot.png\"></a>" ;
2020-10-11 19:25:54 +00:00
}
elsif ( $ swstate eq "on" ) {
2020-04-20 14:55:36 +00:00
$ swicon = "<a onClick=$cmdauto><img src=\"$FW_ME/www/images/default/10px-kreis-gruen.png\"></a>" ;
2020-10-11 19:25:54 +00:00
}
elsif ( $ swstate =~ /off.*automatic.*/ix ) {
2020-04-20 14:55:36 +00:00
$ swicon = "<a onClick=$cmdon><img src=\"$FW_ME/www/images/default/10px-kreis-gelb.png\"></a>" ;
}
2019-06-08 11:41:49 +00:00
if ( $ legend_style eq 'icon' ) { # mögliche Umbruchstellen mit normalen Blanks vorsehen !
2019-06-07 13:30:17 +00:00
$ legend_txt . = $ txt . ' ' . FW_makeImage ( $ im ) . ' ' . $ swicon . ' ' ;
2020-10-11 19:25:54 +00:00
}
else {
2019-05-30 07:44:31 +00:00
my ( undef , $ co ) = split ( '\@' , $ im ) ;
2020-10-11 19:25:54 +00:00
$ co = '#cccccc' if ( ! $ co ) ; # Farbe per default
$ legend_txt . = '<font color=\'' . $ co . '\'>' . $ txt . '</font> ' . $ swicon . ' ' ; # hier auch Umbruch erlauben
2019-05-30 07:44:31 +00:00
}
}
}
# Parameter f. Anzeige extrahieren
2019-06-24 16:33:11 +00:00
$ maxhours = AttrNum ( $ wlname , 'hourCount' , 24 ) ;
$ hourstyle = AttrVal ( $ wlname , 'hourStyle' , undef ) ;
$ colorv = AttrVal ( $ wlname , 'beamColor' , undef ) ;
$ colorc = AttrVal ( $ wlname , 'beamColor2' , '000000' ) ; # schwarz wenn keine Userauswahl;
$ icon = AttrVal ( $ wlname , 'consumerAdviceIcon' , undef ) ;
$ html_start = AttrVal ( $ wlname , 'htmlStart' , undef ) ; # beliebige HTML Strings die vor der Grafik ausgegeben werden
$ html_end = AttrVal ( $ wlname , 'htmlEnd' , undef ) ; # beliebige HTML Strings die nach der Grafik ausgegeben werden
$ type = AttrVal ( $ wlname , 'layoutType' , 'pv' ) ;
$ kw = AttrVal ( $ wlname , 'Wh/kWh' , 'Wh' ) ;
$ height = AttrNum ( $ wlname , 'beamHeight' , 200 ) ;
$ width = AttrNum ( $ wlname , 'beamWidth' , 6 ) ; # zu klein ist nicht problematisch
2019-06-03 23:00:53 +00:00
$ w = $ width * $ maxhours ; # gesammte Breite der Ausgabe , WetterIcon braucht ca. 34px
2019-06-24 16:33:11 +00:00
$ fsize = AttrNum ( $ wlname , 'spaceSize' , 24 ) ;
$ maxVal = AttrNum ( $ wlname , 'maxPV' , 0 ) ; # dyn. Anpassung der Balkenhöhe oder statisch ?
2019-06-03 23:00:53 +00:00
2019-06-24 16:33:11 +00:00
$ show_night = AttrNum ( $ wlname , 'showNight' , 0 ) ; # alle Balken (Spalten) anzeigen ?
$ show_diff = AttrVal ( $ wlname , 'showDiff' , 'no' ) ; # zusätzliche Anzeige $di{} in allen Typen
$ weather = AttrNum ( $ wlname , 'showWeather' , 1 ) ;
$ colorw = AttrVal ( $ wlname , 'weatherColor' , undef ) ;
2019-06-03 23:00:53 +00:00
2019-06-24 16:33:11 +00:00
$ wlalias = AttrVal ( $ wlname , 'alias' , $ wlname ) ;
2019-07-02 21:03:59 +00:00
$ header = AttrNum ( $ wlname , 'showHeader' , 1 ) ;
2020-04-20 14:55:36 +00:00
$ hdrAlign = AttrVal ( $ wlname , 'headerAlignment' , 'center' ) ; # ermöglicht per attr die Ausrichtung der Tabelle zu setzen
$ hdrDetail = AttrVal ( $ wlname , 'headerDetail' , 'all' ) ; # ermöglicht den Inhalt zu begrenzen, um bspw. passgenau in ftui einzubetten
2019-05-30 07:44:31 +00:00
2019-05-01 20:38:02 +00:00
# Icon Erstellung, mit @<Farbe> ergänzen falls einfärben
# Beispiel mit Farbe: $icon = FW_makeImage('light_light_dim_100.svg@green');
2019-05-30 07:44:31 +00:00
$ icon = FW_makeImage ( $ icon ) if ( defined ( $ icon ) ) ;
2020-06-20 08:02:04 +00:00
my $ co4h = ReadingsNum ( $ name , "${fmin}_Next04Hours_Consumption" , 0 ) ;
2020-07-12 06:09:34 +00:00
my $ coRe = ReadingsNum ( $ name , "${fmin}_RestOfDay_Consumption" , 0 ) ;
my $ coTo = ReadingsNum ( $ name , "${fmin}_Tomorrow_Consumption" , 0 ) ;
my $ coCu = ReadingsNum ( $ name , "${ldlv}_GridConsumption" , 0 ) ;
2019-05-30 07:44:31 +00:00
2020-07-12 06:09:34 +00:00
my $ pv4h = ReadingsNum ( $ name , "${fmin}_Next04Hours_PV" , 0 ) ;
my $ pvRe = ReadingsNum ( $ name , "${fmin}_RestOfDay_PV" , 0 ) ;
my $ pvTo = ReadingsNum ( $ name , "${fmin}_Tomorrow_PV" , 0 ) ;
my $ pvCu = ReadingsNum ( $ name , "${ldlv}_PV" , 0 ) ;
2019-05-30 07:44:31 +00:00
2019-06-13 19:20:53 +00:00
if ( $ kw eq 'kWh' ) {
$ co4h = sprintf ( "%.1f" , $ co4h / 1000 ) . " kWh" ;
$ coRe = sprintf ( "%.1f" , $ coRe / 1000 ) . " kWh" ;
$ coTo = sprintf ( "%.1f" , $ coTo / 1000 ) . " kWh" ;
2020-05-28 07:58:16 +00:00
$ coCu = sprintf ( "%.1f" , $ coCu / 1000 ) . " kW" ;
2019-06-13 19:20:53 +00:00
$ pv4h = sprintf ( "%.1f" , $ pv4h / 1000 ) . " kWh" ;
$ pvRe = sprintf ( "%.1f" , $ pvRe / 1000 ) . " kWh" ;
$ pvTo = sprintf ( "%.1f" , $ pvTo / 1000 ) . " kWh" ;
2020-05-28 07:58:16 +00:00
$ pvCu = sprintf ( "%.1f" , $ pvCu / 1000 ) . " kW" ;
2020-10-11 19:25:54 +00:00
}
else {
2019-06-13 19:20:53 +00:00
$ co4h . = " Wh" ;
$ coRe . = " Wh" ;
$ coTo . = " Wh" ;
2020-05-28 07:58:16 +00:00
$ coCu . = " W" ;
2019-06-13 19:20:53 +00:00
$ pv4h . = " Wh" ;
$ pvRe . = " Wh" ;
$ pvTo . = " Wh" ;
2020-05-28 07:58:16 +00:00
$ pvCu . = " W" ;
2019-05-30 07:44:31 +00:00
}
2019-07-07 18:54:56 +00:00
# Headerzeile generieren
2019-05-30 07:44:31 +00:00
if ( $ header ) {
2020-08-08 14:20:55 +00:00
my $ lang = AttrVal ( "global" , "language" , "EN" ) ;
my $ alias = AttrVal ( $ name , "alias" , "SMA Sunny Portal" ) ; # Linktext als Aliasname oder "SMA Sunny Portal"
2020-04-20 14:55:36 +00:00
my $ dlink = "<a href=\"/fhem?detail=$name\">$alias</a>" ;
2020-06-20 08:02:04 +00:00
my $ lup = ReadingsTimestamp ( $ name , "${fmin}_ForecastToday_Consumption" , "0000-00-00 00:00:00" ) ; # letzter Forecast Update
2020-04-20 14:55:36 +00:00
my $ lupt = "last update:" ;
2020-07-12 06:09:34 +00:00
my $ lblPv4h = "next 4h:" ;
2020-04-20 14:55:36 +00:00
my $ lblPvRe = "today:" ;
my $ lblPvTo = "tomorrow:" ;
2020-05-28 07:58:16 +00:00
my $ lblPvCu = "actual" ;
2020-04-20 14:55:36 +00:00
if ( AttrVal ( "global" , "language" , "EN" ) eq "DE" ) { # Header globales Sprachschema Deutsch
2020-04-21 11:37:45 +00:00
$ lupt = "Stand:" ;
2020-07-12 06:09:34 +00:00
$ lblPv4h = encode ( "utf8" , "nächste 4h:" ) ;
2020-04-21 11:37:45 +00:00
$ lblPvRe = "heute:" ;
$ lblPvTo = "morgen:" ;
2020-05-28 07:58:16 +00:00
$ lblPvCu = "aktuell" ;
2020-04-20 14:55:36 +00:00
}
$ header = "<table align=\"$hdrAlign\">" ;
2020-08-08 14:20:55 +00:00
# Header Link + Status + Update Button
2020-04-20 14:55:36 +00:00
if ( $ hdrDetail eq "all" || $ hdrDetail eq "statusLink" ) {
2020-08-09 07:32:15 +00:00
my ( $ year , $ month , $ day , $ time ) = $ lup =~ /(\d{4})-(\d{2})-(\d{2})\s+(.*)/x ;
if ( AttrVal ( "global" , "language" , "EN" ) eq "DE" ) {
$ lup = "$day.$month.$year $time" ;
2020-10-11 19:25:54 +00:00
}
else {
2020-08-09 07:32:15 +00:00
$ lup = "$year-$month-$day $time" ;
}
2020-08-08 14:20:55 +00:00
my $ cmdupdate = "\"FW_cmd('$FW_ME$FW_subdir?XHR=1&cmd=set $name getData')\"" ; # Update Button generieren
if ( $ ftui && $ ftui eq "ftui" ) {
$ cmdupdate = "\"ftui.setFhemStatus('set $name getData')\"" ;
}
my $ upstate = ReadingsVal ( $ name , "state" , "undef" ) ;
my $ upicon = "<img src=\"$FW_ME/www/images/default/1px-spacer.png\">" ;
if ( $ upstate =~ /ok/ix ) {
$ upicon = "<a onClick=$cmdupdate><img src=\"$FW_ME/www/images/default/10px-kreis-gruen.png\"></a>" ;
2020-10-11 19:25:54 +00:00
}
elsif ( $ upstate =~ /running/ix ) {
2020-08-08 14:20:55 +00:00
$ upicon = "<img src=\"$FW_ME/www/images/default/10px-kreis-gelb.png\"></a>" ;
2020-10-11 19:25:54 +00:00
}
else {
2020-08-08 14:20:55 +00:00
$ upicon = "<a onClick=$cmdupdate><img src=\"$FW_ME/www/images/default/10px-kreis-rot.png\"></a>" ;
}
2020-08-09 06:06:27 +00:00
$ header . = "<tr><td colspan=\"3\" align=\"left\"><b>" . $ dlink . "</b></td><td colspan=\"3\" align=\"right\">(" . $ lupt . " " . $ lup . ") " . $ upicon . "</td></tr>" ;
2019-05-30 07:44:31 +00:00
}
2020-04-20 14:55:36 +00:00
# Header Information pv
if ( $ hdrDetail eq "all" || $ hdrDetail eq "pv" || $ hdrDetail eq "pvco" ) {
2020-05-28 07:58:16 +00:00
$ header . = "<tr>" ;
$ header . = "<td><b>PV =></b></td>" ;
2020-07-06 21:31:24 +00:00
$ header . = "<td><b>$lblPvCu</b></td> <td align=right>$pvCu</td>" if ( $ subs { $ name } { liveData } { doit } ) ;
2020-05-28 07:58:16 +00:00
$ header . = "<td><b>$lblPv4h</b></td> <td align=right>$pv4h</td>" ;
$ header . = "<td><b>$lblPvRe</b></td> <td align=right>$pvRe</td>" ;
$ header . = "<td><b>$lblPvTo</b></td> <td align=right>$pvTo</td>" ;
$ header . = "</tr>" ;
2020-04-20 14:55:36 +00:00
}
# Header Information co
if ( $ hdrDetail eq "all" || $ hdrDetail eq "co" || $ hdrDetail eq "pvco" ) {
2020-05-28 07:58:16 +00:00
$ header . = "<tr>" ;
$ header . = "<td><b>CO =></b></td>" ;
2020-07-06 21:31:24 +00:00
$ header . = "<td><b>$lblPvCu</b></td> <td align=right>$coCu</td>" if ( $ subs { $ name } { liveData } { doit } ) ;
2020-05-28 07:58:16 +00:00
$ header . = "<td><b>$lblPv4h</b></td> <td align=right>$co4h</td>" ;
$ header . = "<td><b>$lblPvRe</b></td> <td align=right>$coRe</td>" ;
$ header . = "<td><b>$lblPvTo</b></td> <td align=right>$coTo</td>" ;
$ header . = "</tr>" ;
2020-04-20 14:55:36 +00:00
}
$ header . = "</table>" ;
2019-05-01 14:30:09 +00:00
}
2019-05-30 07:44:31 +00:00
2019-05-01 14:30:09 +00:00
# Werte aktuelle Stunde
2020-06-20 08:02:04 +00:00
$ pv { 0 } = ReadingsNum ( $ name , "${fmin}_ThisHour_PvMeanPower" , 0 ) ;
$ co { 0 } = ReadingsNum ( $ name , "${fmin}_ThisHour_Consumption" , 0 ) ;
2019-05-30 07:44:31 +00:00
$ di { 0 } = $ pv { 0 } - $ co { 0 } ;
2020-06-20 08:02:04 +00:00
$ is { 0 } = ( ReadingsVal ( $ name , "${fmin}_ThisHour_IsConsumptionRecommended" , 'no' ) eq 'yes' ) ? $ icon : undef ;
$ we { 0 } = $ hash - > { HELPER } { "${fmin}_ThisHour_WeatherId" } if ( $ weather ) ; # für Wettericons
2020-06-11 13:22:34 +00:00
$ we { 0 } = $ we { 0 } // 999 ;
2019-05-30 07:44:31 +00:00
2019-05-01 14:30:09 +00:00
if ( AttrVal ( "global" , "language" , "EN" ) eq "DE" ) {
2020-06-20 08:02:04 +00:00
( undef , undef , undef , $ t { 0 } ) = ReadingsVal ( $ name , "${fmin}_ThisHour_Time" , '0' ) =~ m/(\d{2}).(\d{2}).(\d{4})\s(\d{2})/x ;
2020-10-11 19:25:54 +00:00
}
else {
2020-06-20 08:02:04 +00:00
( undef , undef , undef , $ t { 0 } ) = ReadingsVal ( $ name , "${fmin}_ThisHour_Time" , '0' ) =~ m/(\d{4})-(\d{2})-(\d{2})\s(\d{2})/x ;
2019-05-01 14:30:09 +00:00
}
2019-05-30 07:44:31 +00:00
$ t { 0 } = int ( $ t { 0 } ) ; # zum Rechnen Integer ohne führende Null
###########################################################
# get consumer list and display it in portalGraphics
2020-06-03 13:33:11 +00:00
for ( @ pgCDev ) {
2019-05-30 07:44:31 +00:00
my ( $ itemName , undef ) = split ( ':' , $ _ ) ;
2020-04-20 14:55:36 +00:00
$ itemName =~ s/^\s+|\s+$//gx ; #trim it, if blanks were used
$ _ =~ s/^\s+|\s+$//gx ; #trim it, if blanks were used
2019-05-30 07:44:31 +00:00
#check if listed device is planned
2020-07-12 06:09:34 +00:00
if ( ReadingsVal ( $ name , "${fmaj}_" . $ itemName . "_Planned" , "no" ) eq "yes" ) {
2019-05-30 07:44:31 +00:00
#get start and end hour
my ( $ start , $ end ) ; # werden auf Balken Pos 0 - 23 umgerechnet, nicht auf Stunde !!, Pos = 24 -> ungültige Pos = keine Anzeige
2019-05-01 14:30:09 +00:00
if ( AttrVal ( "global" , "language" , "EN" ) eq "DE" ) {
2020-07-12 06:09:34 +00:00
( undef , undef , undef , $ start ) = ReadingsVal ( $ name , "${fmaj}_" . $ itemName . "_PlannedOpTimeBegin" , '00.00.0000 24' ) =~ m/(\d{2}).(\d{2}).(\d{4})\s(\d{2})/x ;
( undef , undef , undef , $ end ) = ReadingsVal ( $ name , "${fmaj}_" . $ itemName . "_PlannedOpTimeEnd" , '00.00.0000 24' ) =~ m/(\d{2}).(\d{2}).(\d{4})\s(\d{2})/x ;
2020-10-11 19:25:54 +00:00
}
else {
2020-07-12 06:09:34 +00:00
( undef , undef , undef , $ start ) = ReadingsVal ( $ name , "${fmaj}_" . $ itemName . "_PlannedOpTimeBegin" , '0000-00-00 24' ) =~ m/(\d{4})-(\d{2})-(\d{2})\s(\d{2})/x ;
( undef , undef , undef , $ end ) = ReadingsVal ( $ name , "${fmaj}_" . $ itemName . "_PlannedOpTimeEnd" , '0000-00-00 24' ) =~ m/(\d{4})-(\d{2})-(\d{2})\s(\d{2})/x ;
2019-05-30 07:44:31 +00:00
}
2020-04-21 11:37:45 +00:00
$ start = int ( $ start ) ;
$ end = int ( $ end ) ;
2019-06-08 16:29:17 +00:00
my $ flag = 0 ; # default kein Tagesverschieber
2019-05-30 07:44:31 +00:00
#correct the hour for accurate display
2019-06-08 16:29:17 +00:00
if ( $ start < $ t { 0 } ) { # consumption seems to be tomorrow
$ start = 24 - $ t { 0 } + $ start ;
2020-04-21 11:37:45 +00:00
$ flag = 1 ;
2020-10-11 19:25:54 +00:00
}
else {
2019-06-08 16:29:17 +00:00
$ start -= $ t { 0 } ;
2019-05-30 07:44:31 +00:00
}
2019-06-08 16:29:17 +00:00
if ( $ flag ) { # consumption seems to be tomorrow
$ end = 24 - $ t { 0 } + $ end ;
2020-10-11 19:25:54 +00:00
}
else {
2019-05-30 07:44:31 +00:00
$ end -= $ t { 0 } ;
}
$ _ . = ":" . $ start . ":" . $ end ;
2020-10-11 19:25:54 +00:00
}
else {
2019-05-30 07:44:31 +00:00
$ _ . = ":24:24" ;
}
2019-06-08 16:29:17 +00:00
Log3 ( $ name , 4 , "$name - Consumer planned data: $_" ) ;
2019-05-30 07:44:31 +00:00
}
$ maxVal = ( ! $ maxVal ) ? $ pv { 0 } : $ maxVal ; # Startwert wenn kein Wert bereits via attr vorgegeben ist
$ maxCon = $ co { 0 } ; # für Typ co
2019-06-03 23:00:53 +00:00
$ maxDif = $ di { 0 } ; # für Typ diff
$ minDif = $ di { 0 } ; # für Typ diff
2019-05-30 07:44:31 +00:00
2020-04-20 14:55:36 +00:00
for my $ i ( 1 .. $ maxhours - 1 ) {
2020-06-20 08:02:04 +00:00
$ pv { $ i } = ReadingsNum ( $ name , "${fmaj}_NextHour" . sprintf ( "%02d" , $ i ) . "_PvMeanPower" , 0 ) ; # Erzeugung
$ co { $ i } = ReadingsNum ( $ name , "${fmaj}_NextHour" . sprintf ( "%02d" , $ i ) . "_Consumption" , 0 ) ; # Verbrauch
2019-05-30 07:44:31 +00:00
$ di { $ i } = $ pv { $ i } - $ co { $ i } ;
$ maxVal = $ pv { $ i } if ( $ pv { $ i } > $ maxVal ) ;
$ maxCon = $ co { $ i } if ( $ co { $ i } > $ maxCon ) ;
$ maxDif = $ di { $ i } if ( $ di { $ i } > $ maxDif ) ;
$ minDif = $ di { $ i } if ( $ di { $ i } < $ minDif ) ;
2020-06-20 08:02:04 +00:00
$ is { $ i } = ( ReadingsVal ( $ name , "${fmaj}_NextHour" . sprintf ( "%02d" , $ i ) . "_IsConsumptionRecommended" , 'no' ) eq 'yes' ) ? $ icon : undef ;
$ we { $ i } = $ hash - > { HELPER } { "${fmaj}_NextHour" . sprintf ( "%02d" , $ i ) . "_WeatherId" } if ( $ weather ) ; # für Wettericons
2020-06-11 13:22:34 +00:00
$ we { $ i } = $ we { $ i } // 999 ;
2019-05-30 07:44:31 +00:00
if ( AttrVal ( "global" , "language" , "EN" ) eq "DE" ) {
2020-06-20 08:02:04 +00:00
( undef , undef , undef , $ t { $ i } ) = ReadingsVal ( $ name , "${fmaj}_NextHour" . sprintf ( "%02d" , $ i ) . "_Time" , '0' ) =~ m/(\d{2}).(\d{2}).(\d{4})\s(\d{2})/x ;
2020-10-11 19:25:54 +00:00
}
else {
2020-06-20 08:02:04 +00:00
( undef , undef , undef , $ t { $ i } ) = ReadingsVal ( $ name , "${fmaj}_NextHour" . sprintf ( "%02d" , $ i ) . "_Time" , '0' ) =~ m/(\d{4})-(\d{2})-(\d{2})\s(\d{2})/x ;
2019-05-30 07:44:31 +00:00
}
2019-05-31 07:55:21 +00:00
$ t { $ i } = int ( $ t { $ i } ) ; # keine führende 0
2019-05-30 07:44:31 +00:00
}
######################################
# Tabellen Ausgabe erzeugen
# Wenn Table class=block alleine steht, zieht es bei manchen Styles die Ausgabe auf 100% Seitenbreite
# lässt sich durch einbetten in eine zusätzliche Table roomoverview eindämmen
# Die Tabelle ist recht schmal angelegt, aber nur so lassen sich Umbrüche erzwingen
2019-06-03 23:00:53 +00:00
$ ret = "<html>" ;
$ ret . = $ html_start if ( defined ( $ html_start ) ) ;
2019-05-30 07:44:31 +00:00
$ ret . = "<style>TD.smaportal {text-align: center; padding-left:1px; padding-right:1px; margin:0px;}</style>" ;
2019-06-03 23:00:53 +00:00
$ ret . = "<table class='roomoverview' width='$w' style='width:" . $ w . "px'><tr class='devTypeTr'></tr>" ;
2019-05-30 07:44:31 +00:00
$ ret . = "<tr><td class='smaportal'>" ;
2019-06-03 23:00:53 +00:00
$ ret . = "\n<table class='block'>" ; # das \n erleichtert das Lesen der debug Quelltextausgabe
2019-05-30 07:44:31 +00:00
if ( $ header ) { # Header ausgeben
$ ret . = "<tr class='odd'>" ;
# mit einem extra <td></td> ein wenig mehr Platz machen, ergibt i.d.R. weniger als ein Zeichen
$ ret . = "<td colspan='" . ( $ maxhours + 2 ) . "' align='center' style='word-break: normal'>$header</td></tr>" ;
}
if ( $ legend_txt && ( $ legend eq 'top' ) ) {
$ ret . = "<tr class='odd'>" ;
$ ret . = "<td colspan='" . ( $ maxhours + 2 ) . "' align='center' style='word-break: normal'>$legend_txt</td></tr>" ;
}
if ( $ weather ) {
2020-04-20 14:55:36 +00:00
$ ret . = "<tr class='even'><td class='smaportal'></td>" ; # freier Platz am Anfang
2019-05-30 07:44:31 +00:00
2020-04-20 14:55:36 +00:00
for my $ i ( 0 .. $ maxhours - 1 ) { # keine Anzeige bei Null Ertrag bzw. in der Nacht , Typ pcvo & diff haben aber immer Daten in der Nacht
if ( $ pv { $ i } || $ show_night || ( $ type eq 'pvco' ) || ( $ type eq 'diff' ) ) {
2019-05-30 07:44:31 +00:00
# FHEM Wetter Icons (weather_xxx) , Skalierung und Farbe durch FHEM Bordmittel
my $ icon_name = weather_icon ( $ we { $ i } ) ; # unknown -> FHEM Icon Fragezeichen im Kreis wird als Ersatz Icon ausgegeben
Log3 ( $ name , 3 , "$name - unknown SMA Portal weather id: " . $ we { $ i } . ", please inform the maintainer" ) if ( $ icon_name eq 'unknown' ) ;
$ icon_name . = '@' . $ colorw if ( defined ( $ colorw ) ) ;
$ val = FW_makeImage ( $ icon_name ) ;
$ val = '<b>???<b/>' if ( $ val eq $ icon_name ) ; # passendes Icon beim User nicht vorhanden ! ( attr web iconPath falsch/prüfen/update ? )
$ ret . = "<td class='smaportal' width='$width' style='margin:1px; vertical-align:middle align:center; padding-bottom:1px;'>$val</td>" ;
2020-04-20 14:55:36 +00:00
2020-10-11 19:25:54 +00:00
}
else { # Kein Ertrag oder show_night = 0
2019-05-30 07:44:31 +00:00
$ ret . = "<td></td>" ; $ we { $ i } = undef ;
}
2020-04-20 14:55:36 +00:00
# mit $we{$i} = undef kann man unten leicht feststellen ob für diese Spalte bereits ein Icon ausgegeben wurde oder nicht
}
2019-05-30 07:44:31 +00:00
$ ret . = "<td class='smaportal'></td></tr>" ; # freier Platz am Ende der Icon Zeile
}
2019-05-01 14:30:09 +00:00
2019-05-30 07:44:31 +00:00
if ( $ show_diff eq 'top' ) { # Zusätzliche Zeile Ertrag - Verbrauch
$ ret . = "<tr class='even'><td class='smaportal'></td>" ; # freier Platz am Anfang
2020-04-20 14:55:36 +00:00
for my $ i ( 0 .. $ maxhours - 1 ) {
$ val = formatVal6 ( $ di { $ i } , $ kw , $ we { $ i } ) ;
$ val = ( $ di { $ i } < 0 ) ? '<b>' . $ val . '<b/>' : '+' . $ val ; # negativ Zahlen in Fettschrift
2019-05-30 07:44:31 +00:00
$ ret . = "<td class='smaportal' style='vertical-align:middle; text-align:center;'>$val</td>" ;
}
$ ret . = "<td class='smaportal'></td></tr>" ; # freier Platz am Ende
}
2019-05-01 14:30:09 +00:00
2019-05-30 07:44:31 +00:00
$ ret . = "<tr class='even'><td class='smaportal'></td>" ; # Neue Zeile mit freiem Platz am Anfang
2019-05-01 14:30:09 +00:00
2020-04-20 14:55:36 +00:00
for my $ i ( 0 .. $ maxhours - 1 ) {
2019-05-30 07:44:31 +00:00
# Achtung Falle, Division by Zero möglich,
# maxVal kann gerade bei kleineren maxhours Ausgaben in der Nacht leicht auf 0 fallen
2019-06-03 23:00:53 +00:00
$ height = 200 if ( ! $ height ) ; # Fallback, sollte eigentlich nicht vorkommen, außer der User setzt es auf 0
2019-05-30 07:44:31 +00:00
$ maxVal = 1 if ( ! $ maxVal ) ;
$ maxCon = 1 if ( ! $ maxCon ) ;
2019-05-01 14:30:09 +00:00
2019-05-30 07:44:31 +00:00
# Der zusätzliche Offset durch $fsize verhindert bei den meisten Skins
# dass die Grundlinie der Balken nach unten durchbrochen wird
if ( $ type eq 'co' ) {
$ he = int ( ( $ maxCon - $ co { $ i } ) / $ maxCon * $ height ) + $ fsize ; # he - freier der Raum über den Balken.
$ z3 = int ( $ height + $ fsize - $ he ) ; # Resthöhe
2020-10-11 19:25:54 +00:00
}
elsif ( $ type eq 'pv' ) {
2019-05-30 07:44:31 +00:00
$ he = int ( ( $ maxVal - $ pv { $ i } ) / $ maxVal * $ height ) + $ fsize ;
$ z3 = int ( $ height + $ fsize - $ he ) ;
2020-10-11 19:25:54 +00:00
}
elsif ( $ type eq 'pvco' ) {
2019-05-30 07:44:31 +00:00
# Berechnung der Zonen
# he - freier der Raum über den Balken. fsize wird nicht verwendet, da bei diesem Typ keine Zahlen über den Balken stehen
# z2 - der Ertrag ggf mit Icon
# z3 - der Verbrauch , bei zu kleinem Wert wird der Platz komplett Zone 2 zugeschlagen und nicht angezeigt
# z2 und z3 nach Bedarf tauschen, wenn der Verbrauch größer als der Ertrag ist
$ maxVal = $ maxCon if ( $ maxCon > $ maxVal ) ; # wer hat den größten Wert ?
if ( $ pv { $ i } > $ co { $ i } ) { # pv oben , co unten
$ z2 = $ pv { $ i } ; $ z3 = $ co { $ i } ;
2020-10-11 19:25:54 +00:00
}
else { # tauschen, Verbrauch ist größer als Ertrag
2019-05-30 07:44:31 +00:00
$ z3 = $ pv { $ i } ; $ z2 = $ co { $ i } ;
2019-05-01 14:30:09 +00:00
}
2019-05-30 07:44:31 +00:00
$ he = int ( ( $ maxVal - $ z2 ) / $ maxVal * $ height ) ;
$ z2 = int ( ( $ z2 - $ z3 ) / $ maxVal * $ height ) ;
2019-05-01 14:30:09 +00:00
2019-05-30 07:44:31 +00:00
$ z3 = int ( $ height - $ he - $ z2 ) ; # was von maxVal noch übrig ist
if ( $ z3 < int ( $ fsize /2)) { # dünnen Strichbalken vermeiden / ca . halbe Zeichenhöhe
$ z2 += $ z3 ; $ z3 = 0 ;
2020-10-11 19:25:54 +00:00
}
}
else { # Typ dif
2019-05-30 07:44:31 +00:00
# Berechnung der Zonen
# he - freier der Raum über den Balken , Zahl positiver Wert + fsize
# z2 - positiver Balken inkl Icon
# z3 - negativer Balken
# z4 - Zahl negativer Wert + fsize
my ( $ px_pos , $ px_neg ) ;
my $ maxPV = 0 ; # ToDo: maxPV noch aus Attribut maxPV ableiten
if ( $ maxPV ) { # Feste Aufteilung +/- , jeder 50 % bei maxPV = 0
$ px_pos = int ( $ height / 2 ) ;
$ px_neg = $ height - $ px_pos ; # Rundungsfehler vermeiden
2020-10-11 19:25:54 +00:00
}
else { # Dynamische hoch/runter Verschiebung der Null-Linie
2019-06-03 23:00:53 +00:00
if ( $ minDif >= 0 ) { # keine negativen Balken vorhanden, die Positiven bekommen den gesammten Raum
2019-05-30 07:44:31 +00:00
$ px_neg = 0 ;
$ px_pos = $ height ;
2020-10-11 19:25:54 +00:00
}
else {
2019-05-30 07:44:31 +00:00
if ( $ maxDif > 0 ) {
$ px_neg = int ( $ height * abs ( $ minDif ) / ( $ maxDif + abs ( $ minDif ) ) ) ; # Wieviel % entfallen auf unten ?
$ px_pos = $ height - $ px_neg ; # der Rest ist oben
2020-10-11 19:25:54 +00:00
}
else { # keine positiven Balken vorhanden, die Negativen bekommen den gesammten Raum
2019-05-30 07:44:31 +00:00
$ px_neg = $ height ;
$ px_pos = 0 ;
}
}
}
if ( $ di { $ i } >= 0 ) { # Zone 2 & 3 mit ihren direkten Werten vorbesetzen
$ z2 = $ di { $ i } ;
$ z3 = abs ( $ minDif ) ;
2020-10-11 19:25:54 +00:00
}
else {
2019-05-30 07:44:31 +00:00
$ z2 = $ maxDif ;
$ z3 = abs ( $ di { $ i } ) ; # Nur Betrag ohne Vorzeichen
2019-05-01 14:30:09 +00:00
}
2019-05-30 07:44:31 +00:00
# Alle vorbesetzen Werte umrechnen auf echte Ausgabe px
$ he = ( ! $ px_pos ) ? 0 : int ( ( $ maxDif - $ z2 ) / $ maxDif * $ px_pos ) ; # Teilung durch 0 vermeiden
$ z2 = ( $ px_pos - $ he ) ;
2019-05-01 14:30:09 +00:00
2019-05-30 07:44:31 +00:00
$ z4 = ( ! $ px_neg ) ? 0 : int ( ( abs ( $ minDif ) - $ z3 ) / abs ( $ minDif ) * $ px_neg ) ; # Teilung durch 0 unbedingt vermeiden
$ z3 = ( $ px_neg - $ z4 ) ;
# Beiden Zonen die Werte ausgeben könnten muß fsize als zusätzlicher Raum zugeschlagen werden !
$ he += $ fsize ;
$ z4 += $ fsize if ( $ z3 ) ; # komplette Grafik ohne negativ Balken, keine Ausgabe von z3 & z4
2019-05-01 14:30:09 +00:00
}
2019-05-30 07:44:31 +00:00
# das style des nächsten TD bestimmt ganz wesentlich das gesammte Design
# das \n erleichtert das lesen des Seitenquelltext beim debugging
# vertical-align:bottom damit alle Balken und Ausgaben wirklich auf der gleichen Grundlinie sitzen
$ ret . = "<td style='text-align: center; padding-left:1px; padding-right:1px; margin:0px; vertical-align:bottom; padding-top:0px'>\n" ;
if ( ( $ type eq 'pv' ) || ( $ type eq 'co' ) ) {
$ v = ( $ type eq 'co' ) ? $ co { $ i } : $ pv { $ i } ;
$ v = 0 if ( ( $ type eq 'co' ) && ! $ pv { $ i } && ! $ show_night ) ; # auch bei type co die Nacht ggf. unterdrücken
$ val = formatVal6 ( $ v , $ kw , $ we { $ i } ) ;
$ ret . = "<table width='100%' height='100%'>" ; # mit width=100% etwas bessere Füllung der Balken
$ ret . = "<tr class='even' style='height:" . $ he . "px'>" ;
$ ret . = "<td class='smaportal' style='vertical-align:bottom'>" . $ val . "</td></tr>" ;
if ( $ v || $ show_night ) {
# Balken nur einfärben wenn der User via Attr eine Farbe vorgibt, sonst bestimmt class odd von TR alleine die Farbe
my $ style = "style=\"padding-bottom:0px; vertical-align:top; margin-left:auto; margin-right:auto;" ;
2019-06-03 23:00:53 +00:00
$ style . = ( defined ( $ colorv ) ) ? " background-color:#$colorv\"" : '"' ; # Syntaxhilight
2019-05-30 07:44:31 +00:00
$ ret . = "<tr class='odd' style='height:" . $ z3 . "px;'>" ;
$ ret . = "<td align='center' class='smaportal' " . $ style . ">" ;
2019-06-20 17:40:49 +00:00
2020-04-20 14:55:36 +00:00
my $ sicon = 1 ;
2019-06-20 17:40:49 +00:00
$ ret . = $ is { $ i } if ( defined ( $ is { $ i } ) && $ sicon ) ;
2019-05-30 07:44:31 +00:00
##################################
# inject the new icon if defined
2019-06-20 09:03:57 +00:00
$ ret . = consinject ( $ hash , $ i , @ pgCDev ) if ( $ ret ) ;
2019-05-30 07:44:31 +00:00
$ ret . = "</td></tr>" ;
2020-10-11 19:25:54 +00:00
}
}
elsif ( $ type eq 'pvco' ) {
2019-05-30 07:44:31 +00:00
my ( $ color1 , $ color2 , $ style1 , $ style2 ) ;
$ ret . = "<table width='100%' height='100%'>\n" ; # mit width=100% etwas bessere Füllung der Balken
if ( $ he ) { # der Freiraum oben kann beim größten Balken ganz entfallen
$ ret . = "<tr class='even' style='height:" . $ he . "px'><td class='smaportal'></td></tr>" ;
}
if ( $ pv { $ i } > $ co { $ i } ) { # wer ist oben, co pder pv ? Wert und Farbe für Zone 2 & 3 vorbesetzen
$ val = formatVal6 ( $ pv { $ i } , $ kw , $ we { $ i } ) ;
$ color1 = $ colorv ;
$ style1 = "style=\"padding-bottom:0px; padding-top:1px; vertical-align:top; margin-left:auto; margin-right:auto;" ;
$ style1 . = ( defined ( $ color1 ) ) ? " background-color:#$color1\"" : '"' ;
if ( $ z3 ) { # die Zuweisung können wir uns sparen wenn Zone 3 nachher eh nicht ausgegeben wird
$ v = formatVal6 ( $ co { $ i } , $ kw , $ we { $ i } ) ;
$ color2 = $ colorc ;
$ style2 = "style=\"padding-bottom:0px; padding-top:1px; vertical-align:top; margin-left:auto; margin-right:auto;" ;
$ style2 . = ( defined ( $ color2 ) ) ? " background-color:#$color2\"" : '"' ;
2020-10-11 19:25:54 +00:00
}
2019-05-30 07:44:31 +00:00
} else {
$ val = formatVal6 ( $ co { $ i } , $ kw , $ we { $ i } ) ;
$ color1 = $ colorc ;
$ style1 = "style=\"padding-bottom:0px; padding-top:1px; vertical-align:top; margin-left:auto; margin-right:auto;" ;
$ style1 . = ( defined ( $ color1 ) ) ? " background-color:#$color1\"" : '"' ;
if ( $ z3 ) {
$ v = formatVal6 ( $ pv { $ i } , $ kw , $ we { $ i } ) ;
$ color2 = $ colorv ;
$ style2 = "style=\"padding-bottom:0px; padding-top:1px; vertical-align:top; margin-left:auto; margin-right:auto;" ;
$ style2 . = ( defined ( $ color2 ) ) ? " background-color:#$color2\"" : '"' ;
}
}
$ ret . = "<tr class='odd' style='height:" . $ z2 . "px'>" ;
$ ret . = "<td align='center' class='smaportal' " . $ style1 . ">$val" ;
2020-04-20 14:55:36 +00:00
2019-05-30 07:44:31 +00:00
$ ret . = $ is { $ i } if ( defined $ is { $ i } ) ;
2020-04-20 14:55:36 +00:00
2019-06-20 09:03:57 +00:00
##################################
# inject the new icon if defined
$ ret . = consinject ( $ hash , $ i , @ pgCDev ) if ( $ ret ) ;
2020-04-20 14:55:36 +00:00
2019-05-30 07:44:31 +00:00
$ ret . = "</td></tr>" ;
if ( $ z3 ) { # die Zone 3 lassen wir bei zu kleinen Werten auch ganz weg
$ ret . = "<tr class='odd' style='height:" . $ z3 . "px'>" ;
$ ret . = "<td align='center' class='smaportal' " . $ style2 . ">$v</td></tr>" ;
}
2020-10-11 19:25:54 +00:00
}
else { # Type dif
2019-05-30 07:44:31 +00:00
my $ style = "style=\"padding-bottom:0px; padding-top:1px; vertical-align:top; margin-left:auto; margin-right:auto;" ;
$ ret . = "<table width='100%' border='0'>\n" ; # Tipp : das nachfolgende border=0 auf 1 setzen hilft sehr Ausgabefehler zu endecken
$ val = ( $ di { $ i } >= 0 ) ? formatVal6 ( $ di { $ i } , $ kw , $ we { $ i } ) : '' ;
$ val = ' 0 ' if ( $ di { $ i } == 0 ) ; # Sonderfall , hier wird die 0 gebraucht !
if ( $ val ) {
$ ret . = "<tr class='even' style='height:" . $ he . "px'>" ;
$ ret . = "<td class='smaportal' style='vertical-align:bottom'>" . $ val . "</td></tr>" ;
}
if ( $ di { $ i } >= 0 ) { # mit Farbe 1 colorv füllen
$ style . = ( defined ( $ colorv ) ) ? " background-color:#$colorv\"" : '"' ;
$ z2 = 1 if ( $ di { $ i } == 0 ) ; # Sonderfall , 1px dünnen Strich ausgeben
$ ret . = "<tr class='odd' style='height:" . $ z2 . "px'>" ;
$ ret . = "<td align='center' class='smaportal' " . $ style . ">" ;
$ ret . = $ is { $ i } if ( defined $ is { $ i } ) ;
$ ret . = "</td></tr>" ;
2020-10-11 19:25:54 +00:00
}
else { # ohne Farbe
2019-05-30 07:44:31 +00:00
$ z2 = 2 if ( $ di { $ i } == 0 ) ; # Sonderfall, hier wird die 0 gebraucht !
if ( $ z2 && $ val ) { # z2 weglassen wenn nicht unbedigt nötig bzw. wenn zuvor he mit val keinen Wert hatte
$ ret . = "<tr class='even' style='height:" . $ z2 . "px'>" ;
$ ret . = "<td class='smaportal'></td></tr>" ;
}
}
if ( $ di { $ i } < 0 ) { # Negativ Balken anzeigen ?
$ style . = ( defined ( $ colorc ) ) ? " background-color:#$colorc\"" : '"' ; # mit Farbe 2 colorc füllen
2020-04-20 14:55:36 +00:00
$ ret . = "<tr class='odd' style='height:" . $ z3 . "px'>" ;
$ ret . = "<td align='center' class='smaportal' " . $ style . "></td></tr>" ;
2020-10-11 19:25:54 +00:00
}
elsif ( $ z3 ) { # ohne Farbe
2019-05-30 07:44:31 +00:00
$ ret . = "<tr class='even' style='height:" . $ z3 . "px'>" ;
$ ret . = "<td class='smaportal'></td></tr>" ;
}
if ( $ z4 ) { # kann entfallen wenn auch z3 0 ist
2020-04-20 14:55:36 +00:00
$ val = ( $ di { $ i } < 0 ) ? formatVal6 ( $ di { $ i } , $ kw , $ we { $ i } ) : ' ' ;
2019-05-30 07:44:31 +00:00
$ ret . = "<tr class='even' style='height:" . $ z4 . "px'>" ;
$ ret . = "<td class='smaportal' style='vertical-align:top'>" . $ val . "</td></tr>" ;
}
}
if ( $ show_diff eq 'bottom' ) { # zusätzliche diff Anzeige
$ val = formatVal6 ( $ di { $ i } , $ kw , $ we { $ i } ) ;
$ val = ( $ di { $ i } < 0 ) ? '<b>' . $ val . '<b/>' : '+' . $ val ; # Kommentar siehe oben bei show_diff eq top
$ ret . = "<tr class='even'><td class='smaportal' style='vertical-align:middle; text-align:center;'>$val</td></tr>" ;
}
2020-04-20 14:55:36 +00:00
$ ret . = "<tr class='even'><td class='smaportal' style='vertical-align:bottom; text-align:center;'>" ;
2019-05-30 07:44:31 +00:00
$ t { $ i } = $ t { $ i } . $ hourstyle if ( defined ( $ hourstyle ) ) ; # z.B. 10:00 statt 10
$ ret . = $ t { $ i } . "</td></tr></table></td>" ; # Stundenwerte ohne führende 0
2019-05-01 14:30:09 +00:00
}
2019-05-30 07:44:31 +00:00
$ ret . = "<td class='smaportal'></td></tr>" ;
###################
# Legende unten
if ( $ legend_txt && ( $ legend eq 'bottom' ) ) {
$ ret . = "<tr class='odd'>" ;
$ ret . = "<td colspan='" . ( $ maxhours + 2 ) . "' align='center' style='word-break: normal'>" ;
$ ret . = "$legend_txt</td></tr>" ;
}
$ ret . = "</table></td></tr></table>" ;
$ ret . = $ html_end if ( defined ( $ html_end ) ) ;
2019-06-03 23:00:53 +00:00
$ ret . = "</html>" ;
2019-05-30 07:44:31 +00:00
2019-05-01 14:30:09 +00:00
return $ ret ;
2019-04-29 22:07:15 +00:00
}
2019-06-20 09:03:57 +00:00
################################################################
# Inject consumer icon
################################################################
2020-04-20 14:55:36 +00:00
sub consinject {
2019-06-20 09:03:57 +00:00
my ( $ hash , $ i , @ pgCDev ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ ret = "" ;
2020-06-03 13:33:11 +00:00
for ( @ pgCDev ) {
2020-04-20 14:55:36 +00:00
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 ) ;
if ( $ im && ( $ i >= $ start ) && ( $ i <= $ end ) ) {
$ ret . = FW_makeImage ( $ im ) ;
}
}
2019-06-20 09:03:57 +00:00
}
2020-04-20 14:55:36 +00:00
2019-06-20 09:03:57 +00:00
return $ ret ;
}
2019-05-30 07:44:31 +00:00
###############################################################################
# Balkenbreite normieren
#
# Die Balkenbreite wird bestimmt durch den Wert.
# Damit alle Balken die gleiche Breite bekommen, müssen die Werte auf
# 6 Ausgabezeichen angeglichen werden.
# "align=center" gleicht gleicht es aus, alternativ könnte man sie auch
# komplett rechtsbündig ausgeben.
# Es ergibt bei fast allen Styles gute Ergebnisse, Ausnahme IOS12 & 6, da diese
# beiden Styles einen recht großen Font benutzen.
# Wird Wetter benutzt, wird die Balkenbreite durch das Icon bestimmt
###############################################################################
2020-04-20 14:55:36 +00:00
sub formatVal6 {
2019-05-30 07:44:31 +00:00
my ( $ v , $ kw , $ w ) = @ _ ;
my $ n = ' ' ; # positive Zahl
if ( $ v < 0 ) {
$ n = '-' ; # negatives Vorzeichen merken
$ v = abs ( $ v ) ;
}
2019-06-13 19:20:53 +00:00
if ( $ kw eq 'kWh' ) { # bei Anzeige in kWh muss weniger aufgefüllt werden
2019-05-30 07:44:31 +00:00
$ v = sprintf ( '%.1f' , ( $ v / 1000 ) ) ;
$ v += 0 ; # keine 0.0 oder 6.0 etc
return ( $ n eq '-' ) ? ( $ v * - 1 ) : $ v if defined ( $ w ) ;
my $ t = $ v - int ( $ v ) ; # Nachkommstelle ?
if ( ! $ t ) { # glatte Zahl ohne Nachkommastelle
if ( ! $ v ) {
return ' ' ; # 0 nicht anzeigen, passt eigentlich immer bis auf einen Fall im Typ diff
2020-10-11 19:25:54 +00:00
}
elsif ( $ v < 10 ) {
2019-05-30 07:44:31 +00:00
return ' ' . $ n . $ v . ' ' ;
2020-10-11 19:25:54 +00:00
}
else {
2019-05-30 07:44:31 +00:00
return ' ' . $ n . $ v . ' ' ;
}
2020-10-11 19:25:54 +00:00
}
else { # mit Nachkommastelle -> zwei Zeichen mehr .X
2019-05-30 07:44:31 +00:00
if ( $ v < 10 ) {
return ' ' . $ n . $ v . ' ' ;
2020-10-11 19:25:54 +00:00
}
else {
2019-05-30 07:44:31 +00:00
return $ n . $ v . ' ' ;
}
}
}
return ( $ n eq '-' ) ? ( $ v * - 1 ) : $ v if defined ( $ w ) ;
# Werte bleiben in Watt
2020-06-21 05:44:26 +00:00
if ( ! $ v ) { return ' ' ; } ## no critic "Cascading" # keine Anzeige bei Null
2019-05-30 07:44:31 +00:00
elsif ( $ v < 10 ) { return ' ' . $ n . $ v . ' ' ; } # z.B. 0
elsif ( $ v < 100 ) { return ' ' . $ n . $ v . ' ' ; }
elsif ( $ v < 1000 ) { return ' ' . $ n . $ v . ' ' ; }
elsif ( $ v < 10000 ) { return $ n . $ v . ' ' ; }
else { return $ n . $ v ; } # mehr als 10.000 W :)
}
###############################################################################
# Zuordungstabelle "WeatherId" angepasst auf FHEM Icons
###############################################################################
2020-04-20 14:55:36 +00:00
sub weather_icon {
2019-05-30 07:44:31 +00:00
my $ id = shift ;
my % weather_ids = (
2020-04-20 14:55:36 +00:00
'0' = > 'weather_sun' , # Sonne (klar) # vorhanden
'1' = > 'weather_cloudy_light' , # leichte Bewölkung (1/3) # vorhanden
'2' = > 'weather_cloudy' , # mittlere Bewölkung (2/3) # vorhanden
'3' = > 'weather_cloudy_heavy' , # starke Bewölkung (3/3) # vorhanden
'10' = > 'weather_fog' , # Nebel # neu
'11' = > 'weather_rain_fog' , # Nebel mit Regen # neu
'20' = > 'weather_rain_heavy' , # Regen (viel) # vorhanden
'21' = > 'weather_rain_snow_heavy' , # Regen (viel) mit Schneefall # neu
'30' = > 'weather_rain_light' , # leichter Regen (1 Tropfen) # vorhanden
'31' = > 'weather_rain' , # leichter Regen (2 Tropfen) # vorhanden
'32' = > 'weather_rain_heavy' , # leichter Regen (3 Tropfen) # vorhanden
'40' = > 'weather_rain_snow_light' , # leichter Regen mit Schneefall (1 Tropfen) # neu
'41' = > 'weather_rain_snow' , # leichter Regen mit Schneefall (3 Tropfen) # neu
'50' = > 'weather_snow_light' , # bewölkt mit Schneefall (1 Flocke) # vorhanden
'51' = > 'weather_snow' , # bewölkt mit Schneefall (2 Flocken) # vorhanden
'52' = > 'weather_snow_heavy' , # bewölkt mit Schneefall (3 Flocken) # vorhanden
'60' = > 'weather_rain_light' , # Sonne, Wolke mit Regen (1 Tropfen) # vorhanden
'61' = > 'weather_rain' , # Sonne, Wolke mit Regen (2 Tropfen) # vorhanden
'62' = > 'weather_rain_heavy' , # Sonne, Wolke mit Regen (3 Tropfen) # vorhanden
'70' = > 'weather_snow_light' , # Sonne, Wolke mit Schnee (1 Flocke) # vorhanden
'71' = > 'weather_snow_heavy' , # Sonne, Wolke mit Schnee (3 Flocken) # vorhanden
'80' = > 'weather_thunderstorm' , # Wolke mit Blitz # vorhanden
'81' = > 'weather_storm' , # Wolke mit Blitz und Starkregen # vorhanden
'90' = > 'weather_sun' , # Sonne (klar) # vorhanden
'91' = > 'weather_sun' , # Sonne (klar) wie 90 # vorhanden
'100' = > 'weather_night' , # Mond - Nacht # neu
'101' = > 'weather_night_cloudy_light' , # Mond mit Wolken - # neu
'102' = > 'weather_night_cloudy' , # Wolken mittel (2/2) - Nacht # neu
'103' = > 'weather_night_cloudy_heavy' , # Wolken stark (3/3) - Nacht # neu
'110' = > 'weather_night_fog' , # Nebel - Nacht # neu
'111' = > 'weather_night_rain_fog' , # Nebel mit Regen (3 Tropfen) - Nacht # neu
'120' = > 'weather_night_rain_heavy' , # Regen (viel) - Nacht # neu
'121' = > 'weather_night_snow_rain_heavy' , # Regen (viel) mit Schneefall - Nacht # neu
'130' = > 'weather_night_rain_light' , # leichter Regen (1 Tropfen) - Nacht # neu
'131' = > 'weather_night_rain' , # leichter Regen (2 Tropfen) - Nacht # neu
'132' = > 'weather_night_rain_heavy' , # leichter Regen (3 Tropfen) - Nacht # neu
'140' = > 'weather_night_snow_rain_light' , # leichter Regen mit Schneefall (1 Tropfen) - Nacht # neu
'141' = > 'weather_night_snow_rain_heavy' , # leichter Regen mit Schneefall (3 Tropfen) - Nacht # neu
'150' = > 'weather_night_snow_light' , # bewölkt mit Schneefall (1 Flocke) - Nacht # neu
'151' = > 'weather_night_snow' , # bewölkt mit Schneefall (2 Flocken) - Nacht # neu
'152' = > 'weather_night_snow_heavy' , # bewölkt mit Schneefall (3 Flocken) - Nacht # neu
'160' = > 'weather_night_rain_light' , # Mond, Wolke mit Regen (1 Tropfen) - Nacht # neu
'161' = > 'weather_night_rain' , # Mond, Wolke mit Regen (2 Tropfen) - Nacht # neu
'162' = > 'weather_night_rain_heavy' , # Mond, Wolke mit Regen (3 Tropfen) - Nacht # neu
'170' = > 'weather_night_snow_rain' , # Mond, Wolke mit Schnee (1 Flocke) - Nacht # neu
'171' = > 'weather_night_snow_heavy' , # Mond, Wolke mit Schnee (3 Flocken) - Nacht # neu
'180' = > 'weather_night_thunderstorm_light' , # Wolke mit Blitz - Nacht # neu
2020-06-11 12:18:13 +00:00
'181' = > 'weather_night_thunderstorm' , # Wolke mit Blitz und Starkregen - Nacht # neu
'999' = > '1px-spacer' # Dummy - keine Anzeige Wettericon # vorhanden
2019-05-30 07:44:31 +00:00
) ;
return $ weather_ids { $ id } if ( defined ( $ weather_ids { $ id } ) ) ;
return 'unknown' ;
}
2019-06-03 23:00:53 +00:00
######################################################################################################
# Refresh eines Raumes aus $hash->{HELPER}{SPGROOM}
2019-08-22 13:36:50 +00:00
# bzw. Longpoll von SMAPortal bzw. eines SMAPortalSPG Devices wenn $hash->{HELPER}{SPGDEV} gefüllt
2019-06-03 23:00:53 +00:00
# $hash, $pload (1=Page reload), SMAPortalSPG-Event (1=Event)
######################################################################################################
2020-04-20 14:55:36 +00:00
sub SPGRefresh {
2019-06-03 23:00:53 +00:00
my ( $ hash , $ pload , $ lpollspg ) = @ _ ;
my $ name ;
if ( ref $ hash ne "HASH" ) {
2020-04-20 14:55:36 +00:00
( $ name , $ pload , $ lpollspg ) = split "," , $ hash ;
$ hash = $ defs { $ name } ;
2020-10-11 19:25:54 +00:00
}
else {
2020-04-20 14:55:36 +00:00
$ name = $ hash - > { NAME } ;
2019-06-03 23:00:53 +00:00
}
my $ fpr = 0 ;
# Kontext des SMAPortalSPG-Devices speichern für Refresh
2020-04-20 14:55:36 +00:00
my $ sd = $ hash - > { HELPER } { SPGDEV } ? $ hash - > { HELPER } { SPGDEV } : "\"n.a.\"" ; # Name des aufrufenden SMAPortalSPG-Devices
my $ sr = $ hash - > { HELPER } { SPGROOM } ? $ hash - > { HELPER } { SPGROOM } : "\"n.a.\"" ; # Raum aus dem das SMAPortalSPG-Device die Funktion aufrief
my $ sl = $ hash - > { HELPER } { SPGDETAIL } ? $ hash - > { HELPER } { SPGDETAIL } : "\"n.a.\"" ; # Name des SMAPortalSPG-Devices (wenn Detailansicht)
$ fpr = AttrVal ( $ hash - > { HELPER } { SPGDEV } , "forcePageRefresh" , 0 ) if ( $ hash - > { HELPER } { SPGDEV } ) ;
2019-06-03 23:00:53 +00:00
Log3 ( $ name , 4 , "$name - Refresh - caller: $sd, callerroom: $sr, detail: $sl, pload: $pload, forcePageRefresh: $fpr, event_Spgdev: $lpollspg" ) ;
# Page-Reload
if ( $ pload && ( $ hash - > { HELPER } { SPGROOM } && ! $ hash - > { HELPER } { SPGDETAIL } && ! $ fpr ) ) {
# trifft zu wenn in einer Raumansicht
my @ rooms = split ( "," , $ hash - > { HELPER } { SPGROOM } ) ;
2020-06-03 13:33:11 +00:00
for ( @ rooms ) {
2019-06-03 23:00:53 +00:00
my $ room = $ _ ;
2020-08-06 19:39:34 +00:00
{ map { FW_directNotify ( "FILTER=room=$room" , "#FHEMWEB:$_" , "location.reload('true')" , "" ) } devspec2array ( "TYPE=FHEMWEB" ) } ## no critic 'void context';
2019-06-03 23:00:53 +00:00
}
2020-10-11 19:25:54 +00:00
}
elsif ( $ pload && ( ! $ hash - > { HELPER } { SPGROOM } || $ hash - > { HELPER } { SPGDETAIL } ) ) {
2019-06-03 23:00:53 +00:00
# trifft zu bei Detailansicht oder im FLOORPLAN bzw. Dashboard oder wenn Seitenrefresh mit dem
# SMAPortalSPG-Attribut "forcePageRefresh" erzwungen wird
2020-08-06 19:39:34 +00:00
{ map { FW_directNotify ( "#FHEMWEB:$_" , "location.reload('true')" , "" ) } devspec2array ( "TYPE=FHEMWEB" ) } ## no critic 'void context';
2020-10-11 19:25:54 +00:00
}
else {
2019-06-03 23:00:53 +00:00
if ( $ fpr ) {
2020-08-06 19:39:34 +00:00
{ map { FW_directNotify ( "#FHEMWEB:$_" , "location.reload('true')" , "" ) } devspec2array ( "TYPE=FHEMWEB" ) } ## no critic 'void context';
2019-06-03 23:00:53 +00:00
}
}
# parentState des SMAPortalSPG-Device updaten
my @ spgs = devspec2array ( "TYPE=SMAPortalSPG" ) ;
my $ st = ReadingsVal ( $ name , "state" , "initialized" ) ;
2020-06-03 13:33:11 +00:00
for ( @ spgs ) {
2019-06-03 23:00:53 +00:00
if ( $ defs { $ _ } { PARENT } eq $ name ) {
next if ( IsDisabled ( $ defs { $ _ } { NAME } ) ) ;
readingsBeginUpdate ( $ defs { $ _ } ) ;
readingsBulkUpdate ( $ defs { $ _ } , "parentState" , $ st ) ;
readingsBulkUpdate ( $ defs { $ _ } , "state" , "updated" ) ;
readingsEndUpdate ( $ defs { $ _ } , 1 ) ;
}
}
return ;
}
2019-03-03 20:08:04 +00:00
1 ;
= pod
2019-03-11 22:54:31 +00:00
= encoding utf8
2019-05-30 07:44:31 +00:00
= item summary Module for communication with the SMA Sunny Portal
= item summary_DE Modul zur Kommunikation mit dem SMA Sunny Portal
2019-03-03 20:08:04 +00:00
= begin html
< a name = "SMAPortal" > </a>
<h3> SMAPortal </h3>
<ul>
2019-06-21 21:24:31 +00:00
2020-12-01 20:51:52 +00:00
With this module , data can be retrieved from the < a href = "https://www.sunnyportal.com" > SMA Sunny Portal </a> and
devices which are registered in the SMA Portal can be controlled . <br> <br>
2019-06-21 21:24:31 +00:00
<ul>
<ul>
<li> Live data ( Consumption and PV - Generation ) </li>
<li> Battery data ( In /Out) and usage data of consumers </ li >
2020-06-20 08:02:04 +00:00
<li> Various balance data and statistical data ( also of the consumers connected to the SMA Sunny Homemanager ) </li>
2019-06-21 21:24:31 +00:00
<li> Weather data delivered from SMA for the facility location </li>
<li> Forecast data ( Consumption and PV - Generation ) inclusive suggestion times to switch comsumers on </li>
<li> the planned times by the Sunny Home Manager to switch consumers on and the current state of consumers ( if present ) </li>
2020-12-01 20:51:52 +00:00
<li> Control of devices registered with SMA Home Manager or SMA Portal </li>
2019-06-21 21:24:31 +00:00
</ul>
</ul>
<br>
2019-07-04 20:02:57 +00:00
Graphic data can also be integrated into FHEM Tablet UI with the
< a href = "https://wiki.fhem.de/wiki/FTUI_Widget_SMAPortalSPG" > "SMAPortalSPG Widget" </a> . <br>
<br>
2019-06-21 21:24:31 +00:00
<b> Preparation </b> <br> <br>
<ul>
This module use the Perl module JSON which has typically to be installed discrete . <br>
2020-04-20 14:55:36 +00:00
On Debian linux based systems that can be done by command: <br> <br>
2019-06-21 21:24:31 +00:00
<code> sudo apt - get install libjson - perl </code> <br> <br>
Subsequent there are an overview of used Perl modules: <br> <br>
POSIX <br>
JSON <br>
Data:: Dumper <br>
Time:: HiRes <br>
Time:: Local <br>
2020-04-20 14:55:36 +00:00
Blocking ( FHEM - Modul ) <br>
2019-06-21 21:24:31 +00:00
GPUtils ( FHEM - Modul ) <br>
FHEM:: Meta ( FHEM - Modul ) <br>
2020-04-20 14:55:36 +00:00
LWP:: UserAgent <br>
HTTP:: Cookies <br>
2019-06-21 21:24:31 +00:00
MIME:: Base64 <br>
Encode <br>
2020-06-20 08:02:04 +00:00
utf8 <br>
2019-06-21 21:24:31 +00:00
<br> <br>
</ul>
2019-03-03 20:08:04 +00:00
2019-06-21 21:24:31 +00:00
< a name = "SMAPortalDefine" > </a>
<b> Definition </b>
<ul>
<br>
A SMAPortal device will be defined by: <br> <br>
2020-04-20 14:55:36 +00:00
2019-06-21 21:24:31 +00:00
<ul>
2020-06-20 11:18:13 +00:00
<b> define & lt ; name & gt ; SMAPortal </b>
2019-06-21 21:24:31 +00:00
</ul>
2020-06-20 11:18:13 +00:00
<br>
2019-06-21 21:24:31 +00:00
After the definition of the device the credentials for the SMA Sunny Portal must be saved with the
following command: <br> <br>
<ul>
2020-06-20 11:18:13 +00:00
<b> set & lt ; name & gt ; credentials & lt ; Username & gt ; & lt ; Password & gt ; </b>
</ul>
<br>
After a successful login , only the asset master data are retrieved .
The attribute < a href = "#providerLevel" > providerLevel </a> is used to set the data suppliers its data
2020-07-03 07:17:50 +00:00
the device should retrieve . If no Sunny Home Manager is installed , the attribute
< a href = "#noHomeManager" > noHomeManager </a> must be set .
2019-06-21 21:24:31 +00:00
</ul>
<br> <br>
< a name = "SMAPortalSet" > </a>
<b> Set </b>
<ul>
<br>
<ul>
2020-06-20 08:02:04 +00:00
< a name = "createPortalGraphic" > </a>
2020-08-06 20:39:49 +00:00
<li> <b> createPortalGraphic & lt ; Generation | Consumption | Generation_Consumption | Differential & gt ; </b> <br>
2019-06-21 21:24:31 +00:00
Creates graphical devices to show the SMA Sunny Portal forecast data in several layouts .
2020-06-20 08:02:04 +00:00
The attribute "providerLevel" must contain "forecastData" . <br>
2019-06-21 21:24:31 +00:00
With the < a href = "#SMAPortalSPGattr" > "attributes of the graphic device" </a> the appearance and coloration of the forecast
data in the created graphic device can be adjusted .
</ul>
2020-06-20 08:02:04 +00:00
</li>
2019-06-21 21:24:31 +00:00
<br>
<ul>
2020-12-01 20:51:52 +00:00
<li> <b> credentials & lt ; username & gt ; & lt ; password & gt ; </b> <br>
2019-06-21 21:24:31 +00:00
Set Username / Password used for the login into the SMA Sunny Portal .
</ul>
2020-12-01 20:51:52 +00:00
</li>
2020-08-08 12:22:12 +00:00
<br>
<ul>
< a name = "getData" > </a>
<li> <b> getData </b> <br>
Identical to the "get data" command . Simplifies the use of the attribute "webCmd" in the FHEMWEB .
</ul>
</li>
2020-12-01 20:51:52 +00:00
<br>
<ul>
< a name = "consumer" > </a>
<li> <b> & lt ; consumer name & gt ; & lt ; on | off | auto | GC & gt ; </b> <br>
The consumers connected to the SMA Sunny Homemanager are offered as soon as they are detected by the module .
Different types of consumers are recognized by the module and consumer specific actions are offered .
<br> <br>
<ul>
<table>
<colgroup> < col width = 25 % > < col width = 75 % > < / colgroup >
<tr> <td> <b> SMA Bluetoth Sockets </b> </td> <td> <b> on </b> switch on , <b> off </b> switch off , <b> auto </b> control by the Sunny Home Manager </td> </tr>
<tr> <td> <b> SMA EV Charger </b> </td> <td> <b> GC </b> Percentage of grid reference to be accepted for charging the electric vehicle ( 0 .. 100 ) </td> </tr>
</table>
</ul>
</li>
</ul>
<br>
2019-06-21 21:24:31 +00:00
</ul>
2020-12-01 20:51:52 +00:00
<br>
2019-06-21 21:24:31 +00:00
< a name = "SMAPortalGet" > </a>
<b> Get </b>
<ul>
<br>
2020-06-20 08:02:04 +00:00
2019-06-21 21:24:31 +00:00
<ul>
2020-06-20 08:02:04 +00:00
< a name = "data" > </a>
2020-08-06 20:39:49 +00:00
<li> <b> data </b> <br>
2019-06-21 21:24:31 +00:00
This command fetch the data from the SMA Sunny Portal manually .
</ul>
2020-06-20 08:02:04 +00:00
</li>
2019-06-21 21:24:31 +00:00
<br>
<ul>
2020-06-20 08:02:04 +00:00
< a name = "storedCredentials" > </a>
2020-08-06 20:39:49 +00:00
<li> <b> storedCredentials </b> <br>
2019-06-21 21:24:31 +00:00
The saved credentials are displayed in a popup window .
</ul>
2020-06-20 08:02:04 +00:00
</li>
2019-06-21 21:24:31 +00:00
</ul>
<br> <br>
< a name = "SMAPortalAttr" > </a>
<b> Attributes </b>
<ul>
<br>
2020-08-06 19:39:34 +00:00
<ul>
< a name = "balanceDay" > </a>
2020-10-11 07:40:33 +00:00
<li> <b> balanceDay & lt ; YYYY - MM - DD & gt ; [ current current - x & lt ; YYYY - MM - DD & gt ; & lt ; YYYY - MM - DD & gt ; ... ] </b> <br>
Defines from which days the data provider "balanceDayData" delivers the data .
In the relative specification <b> current - x </b> is <b> x </b> the number of days that are subtracted from the current day .
The days are separated by spaces , current = current day . <br>
2020-08-08 10:29:35 +00:00
( default: current day ) <br> <br>
<ul>
2020-10-11 07:40:33 +00:00
<b> Examples: </b> <br>
attr & lt ; name & gt ; balanceDay current 2020 - 08 - 07 2020 - 08 - 06 2020 - 08 - 05 <br>
attr & lt ; name & gt ; balanceDay current current - 1 current - 2 <br>
2020-08-08 10:29:35 +00:00
</ul>
2020-08-06 19:39:34 +00:00
</li> <br>
< a name = "balanceMonth" > </a>
2020-10-11 07:40:33 +00:00
<li> <b> balanceMonth & lt ; YYYY - MM & gt ; [ current current - x & lt ; YYYY - MM & gt ; & lt ; YYYY - MM & gt ; ... ] </b> <br>
Defines from which months the data provider "balanceMonthData" delivers the data .
In the relative specification <b> current - x </b> is <b> x </b> the number of months subtracted from the current month .
The month data is separated by spaces , current = current month . <br>
2020-08-08 10:29:35 +00:00
( default: current month ) <br> <br>
<ul>
2020-10-11 07:40:33 +00:00
<b> Examples: </b> <br>
attr & lt ; name & gt ; balanceMonth current 2019 - 07 2019 - 06 2019 - 05 <br>
attr & lt ; name & gt ; balanceMonth current current - 12 current - 24 <br>
2020-08-08 10:29:35 +00:00
</ul>
2020-08-06 19:39:34 +00:00
</li> <br>
< a name = "balanceYear" > </a>
2020-10-11 07:40:33 +00:00
<li> <b> balanceYear & lt ; YYYY & gt ; [ current current - x & lt ; YYYY & gt ; & lt ; YYYY & gt ; & lt ; YYYY & gt ; ... ] </b> <br>
Defines from which years the data provider "balanceYearData" delivers the data .
In the relative specification <b> current - x </b> , <b> x </b> is the number of years that are subtracted from the current year .
The years are separated by spaces , current = current year . <br>
2020-08-08 10:29:35 +00:00
( default: current year ) <br> <br>
<ul>
2020-10-11 07:40:33 +00:00
<b> Examples: </b> <br>
attr & lt ; name & gt ; balanceYear current 2019 2018 2017 <br>
attr & lt ; name & gt ; balanceYear current current - 1 current - 2 <br>
2020-08-08 10:29:35 +00:00
</ul>
2020-08-06 19:39:34 +00:00
</li> <br>
2020-10-10 19:05:38 +00:00
< a name = "cookieDelete" > </a>
<li> <b> cookieDelete </b> <br>
Defines the method of cookie management ( deletion ) . <br>
( default: auto )
<br>
<ul>
<table>
<colgroup> < col width = 5 % > < col width = 95 % > < / colgroup >
<tr> <td> <b> auto </b> </td> <td> - Cookie file is managed according to an internal procedure </td> </tr>
<tr> <td> <b> afterAttempt </b> </td> <td> - Cookie file is deleted after each failed read attempt </td> </tr>
<tr> <td> <b> afterCycle </b> </td> <td> - Cookie file is deleted after each read cycle ( covers several attempts ) </td> </tr>
<tr> <td> <b> afterRun </b> </td> <td> - Cookie file is deleted after each pass of a data retrieval </td> </tr>
<tr> <td> <b> afterAttempt & Run </b> </td> <td> - Cookie file is deleted after each failed read attempt <b> and </b> run through a data retrieval </td> </tr>
</table>
</ul>
</li> <br>
2019-06-21 21:24:31 +00:00
< a name = "cookieLocation" > </a>
<li> <b> cookieLocation & lt ; Pfad /File> </ b > <br>
2020-05-28 11:40:33 +00:00
The path and filename of received Cookies . <br>
( default: . /log/ & lt ; name & gt ; _cookie . txt )
2019-06-21 21:24:31 +00:00
<br> <br>
<ul>
2020-04-20 14:55:36 +00:00
<b> Example: </b> <br>
2019-06-21 21:24:31 +00:00
attr & lt ; name & gt ; cookieLocation . /log/coo kies . txt <br>
</ul>
</li> <br>
< a name = "disable" > </a>
<li> <b> disable </b> <br>
Deactivate /activate the device. </ li > <br>
< a name = "interval" > </a>
<li> <b> interval & lt ; seconds & gt ; </b> <br>
Time interval for continuous data retrieval from the aus dem SMA Sunny Portal ( default: 300 seconds ) . <br>
2020-04-20 14:55:36 +00:00
if the interval is set to "0" , no continuous data retrieval is executed and has to be triggered manually by the
"get <name> data" command . <br> <br>
2020-07-03 07:17:50 +00:00
2020-06-20 08:02:04 +00:00
<ul>
<b> Note: </b>
2020-11-11 16:34:09 +00:00
The retrieval interval must not be less than 180 seconds . As of previous experiences SMA suffers an interval of
2020-06-20 08:02:04 +00:00
120 seconds although the SMA terms and conditions don ' t permit an automatic data fetch by computer programs .
</ul>
</li> <br>
2020-07-03 07:17:50 +00:00
< a name = "noHomeManager" > </a>
<li> <b> noHomeManager </b> <br>
Must be set if no Sunny Home Manager is installed .
</li> <br>
2020-06-20 08:02:04 +00:00
< a name = "plantLogbookApprovalState" > </a>
<li> <b> plantLogbookApprovalState </b> <br>
With this attribute the entries are filtered according to their status . <br>
( default: Any )
<br>
<ul>
<table>
<colgroup> < col width = 5 % > < col width = 95 % > < / colgroup >
<tr> <td> <b> Any </b> </td> <td> - all messages </td> </tr>
<tr> <td> <b> NotApproved </b> </td> <td> - unconfirmed messages </td> </tr>
</table>
</ul>
</li> <br>
< a name = "plantLogbookTypes" > </a>
<li> <b> plantLogbookTypes </b> <br>
This attribute defines the message types of the asset logbook to be selected .
A maximum of the latest 25 logbook entries of all set types are displayed . <br>
( default: Warning , Disturbance , Error )
</li> <br>
< a name = "providerLevel" > </a>
<li> <b> providerLevel </b> <br>
The scope of the data to be generated is set . Asset and consumer master data is always retrieved .
<br> <br>
<ul>
<table>
<colgroup> < col width = 5 % > < col width = 95 % > < / colgroup >
<tr> <td> <b> liveData </b> </td> <td> - generates readings of the current generation and consumption data </td> </tr>
<tr> <td> <b> weatherData </b> </td> <td> - Weather data offered by SMA are retrieved </td> </tr>
2020-07-03 07:17:50 +00:00
<tr> <td> <b> forecastData </b> </td> <td> - Forecast data of generation /consumption and consumer planning data are generated (an SMA Home Manager must be available) </ td > </tr>
2020-06-20 08:02:04 +00:00
<tr> <td> <b> consumerCurrentdata </b> </td> <td> - current consumer data are generated </td> </tr>
<tr> <td> <b> consumerDayData </b> </td> <td> - consumer data day are generated </td> </tr>
<tr> <td> <b> consumerMonthData </b> </td> <td> - consumer data month are generated </td> </tr>
<tr> <td> <b> consumerYearData </b> </td> <td> - consumer data year are generated </td> </tr>
<tr> <td> <b> plantLogbook </b> </td> <td> - the maximum of 25 most recent entries of the plant logbook are retrieved </td> </tr>
2020-08-06 19:39:34 +00:00
<tr> <td> <b> balanceDayData </b> </td> <td> - Statistics data of the day are retrieved ( see attribute < a href = "#balanceDay" > balanceDay </a> ) </td> </tr>
<tr> <td> <b> balanceMonthData </b> </td> <td> - Statistics data of the month are retrieved ( see attribute < a href = "#balanceMonth" > balanceMonth </a> ) </td> </tr>
<tr> <td> <b> balanceYearData </b> </td> <td> - Statistical data of the year are retrieved ( see attribute < a href = "#balanceYear" > balanceYear </a> ) </td> </tr>
2020-07-02 19:45:08 +00:00
<tr> <td> <b> balanceTotalData </b> </td> <td> - Total statistics data are retrieved </td> </tr>
2020-06-20 08:02:04 +00:00
</table>
</ul>
<br>
2019-06-21 21:24:31 +00:00
</li> <br>
< a name = "showPassInLog" > </a>
<li> <b> showPassInLog </b> <br>
If set , the used password will be displayed in Logfile output .
2020-06-20 08:02:04 +00:00
( default: 0 ) </li> <br>
2019-06-21 21:24:31 +00:00
< a name = "userAgent" > </a>
<li> <b> userAgent & lt ; identifier & gt ; </b> <br>
An user agent identifier for identifikation against the SMA Sunny Portal can be specified .
<br> <br>
<ul>
2020-04-20 14:55:36 +00:00
<b> Example: </b> <br>
2019-06-21 21:24:31 +00:00
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>
2020-10-11 19:25:54 +00:00
< a name = "useRelativeNames" > </a>
<li> <b> useRelativeNames </b> <br>
When using relative dates <b> current - x </b> ( see balance . * attributes ) the created reading name contains
also the relative instead of the real date . <br>
( default: real date )
</li> <br>
2019-06-21 21:24:31 +00:00
< a name = "verbose5Data" > </a>
<li> <b> verbose5Data </b> <br>
2020-06-11 20:30:49 +00:00
The attribute value verbose 5 is used to generate very large amounts of data .
The verbose 5 outputs of interest can be selected specifically . <br>
( default: none )
2019-06-21 21:24:31 +00:00
</li> <br>
</ul>
</ul>
<br>
2019-03-03 20:08:04 +00:00
</ul>
= end html
= begin html_DE
< a name = "SMAPortal" > </a>
<h3> SMAPortal </h3>
<ul>
2020-12-01 20:51:52 +00:00
Mit diesem Modul können Daten aus dem < a href = "https://www.sunnyportal.com" > SMA Sunny Portal </a> abgerufen und die am SMA
Home Manager bzw . im SMA Portal registrierten Geräte gesteuert werden . <br> <br>
2019-03-03 20:08:04 +00:00
<ul>
<ul>
<li> Live - Daten ( Verbrauch und PV - Erzeugung ) </li>
2020-06-20 08:02:04 +00:00
<li> verschiedene Bilanzdaten und Statistikdaten ( auch der an den SMA Sunny Homemanager angeschlossenen Verbraucher ) </li>
2019-06-14 12:49:48 +00:00
<li> Batteriedaten ( In /Out) sowie Nutzungsdaten durch Verbraucher </ 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>
2019-06-07 13:30:17 +00:00
<li> die durch den Sunny Home Manager geplanten Schaltzeiten und aktuellen Status von Verbrauchern ( sofern vorhanden ) </li>
2020-12-01 20:51:52 +00:00
<li> Steuerung von am SMA Home Manager bzw . SMA Portal registrierten Geräten </li>
2019-03-03 20:08:04 +00:00
</ul>
</ul>
<br>
2019-07-04 20:02:57 +00:00
Die Portalgrafik kann ebenfalls in FHEM Tablet UI mit dem
< a href = "https://wiki.fhem.de/wiki/FTUI_Widget_SMAPortalSPG" > "SMAPortalSPG Widget" </a> integriert werden . <br>
<br>
2019-03-03 20:08:04 +00:00
<b> Vorbereitung </b> <br> <br>
<ul>
Dieses Modul nutzt das Perl - Modul JSON welches üblicherweise nachinstalliert werden muss . <br>
2020-04-20 14:55:36 +00:00
Auf Debian - Linux basierenden Systemen kann es installiert werden mit: <br> <br>
2019-03-03 20:08:04 +00:00
<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>
2019-03-25 19:46:08 +00:00
Time:: Local <br>
2020-04-20 14:55:36 +00:00
Blocking ( FHEM - Modul ) <br>
2019-03-25 19:46:08 +00:00
GPUtils ( FHEM - Modul ) <br>
FHEM:: Meta ( FHEM - Modul ) <br>
2020-04-20 14:55:36 +00:00
LWP:: UserAgent <br>
HTTP:: Cookies <br>
2019-03-25 19:46:08 +00:00
MIME:: Base64 <br>
Encode <br>
2020-06-20 08:02:04 +00:00
utf8 <br>
2019-03-03 20:08:04 +00:00
<br> <br>
</ul>
< a name = "SMAPortalDefine" > </a>
<b> Definition </b>
<ul>
<br>
Ein SMAPortal - Device wird definiert mit: <br> <br>
2020-04-20 14:55:36 +00:00
2019-03-03 20:08:04 +00:00
<ul>
2020-06-20 11:18:13 +00:00
<b> define & lt ; Name & gt ; SMAPortal </b>
2019-03-03 20:08:04 +00:00
</ul>
2020-06-20 11:18:13 +00:00
<br>
2019-03-03 20:08:04 +00:00
2019-06-21 21:24:31 +00:00
Nach der Definition des Devices müssen die Zugangsparameter für das SMA Sunny Portal gespeichert werden
mit dem Befehl: <br> <br>
2019-03-03 20:08:04 +00:00
<ul>
2020-06-20 11:18:13 +00:00
<b> set & lt ; Name & gt ; credentials & lt ; Username & gt ; & lt ; Passwort & gt ; </b>
</ul>
<br>
Nach einem erfolgreichen Login werden nur die Anlagenstammdaten abgerufen .
Mit dem Attribut < a href = "#providerLevel" > providerLevel </a> werden die Datenlieferanten eingestellt , die durch das
2020-07-03 07:17:50 +00:00
Device abgerufen werden sollen . Ist kein Sunny Home Manager installiert , muss das Attribut
< a href = "#noHomeManager" > noHomeManager </a> gesetzt werden .
2019-03-03 20:08:04 +00:00
</ul>
<br> <br>
< a name = "SMAPortalSet" > </a>
<b> Set </b>
<ul>
<br>
<ul>
2020-06-20 08:02:04 +00:00
< a name = "createPortalGraphic" > </a>
2020-08-06 20:39:49 +00:00
<li> <b> createPortalGraphic & lt ; Generation | Consumption | Generation_Consumption | Differential & gt ; </b> <br>
2019-05-30 07:44:31 +00:00
Erstellt Devices zur grafischen Anzeige der SMA Sunny Portal Prognosedaten in verschiedenen Layouts .
2020-06-20 08:02:04 +00:00
Das Attribut "providerLevel" muss auf den Level "forecastData" enthalten . <br>
2019-06-03 23:00:53 +00:00
Mit den < a href = "#SMAPortalSPGattr" > "Attributen des Grafikdevices" </a> können Erscheinungsbild und
Farbgebung der Prognosedaten in den erstellten Grafik - Devices angepasst werden .
2019-03-03 20:08:04 +00:00
</ul>
2020-06-20 08:02:04 +00:00
</li>
2019-05-01 14:30:09 +00:00
<br>
2019-04-29 22:07:15 +00:00
<ul>
2020-06-20 08:02:04 +00:00
< a name = "credentials" > </a>
2020-08-06 20:39:49 +00:00
<li> <b> credentials & lt ; username & gt ; & lt ; password & gt ; </b> <br>
2019-06-10 16:32:58 +00:00
Setzt Username / Passwort zum Login in das SMA Sunny Portal .
2019-04-29 22:07:15 +00:00
</ul>
2020-06-20 08:02:04 +00:00
</li>
2019-06-12 21:29:10 +00:00
<br>
2020-08-08 12:22:12 +00:00
<ul>
< a name = "getData" > </a>
<li> <b> getData </b> <br>
Identisch zum "get data" Befehl . Vereinfacht die Benutzung des Attributs "webCmd" im FHEMWEB .
</ul>
</li>
<br>
2019-06-12 21:29:10 +00:00
<ul>
2020-06-20 08:02:04 +00:00
< a name = "Verbrauchername" > </a>
2020-12-01 20:51:52 +00:00
<li> <b> & lt ; Verbrauchername & gt ; & lt ; on | off | auto | GC & gt ; </b> <br>
Es werden die an den SMA Sunny Homemanager angeschlossene Verbraucher angeboten sobald sie vom
Modul erkannt wurden . Es werden verschiedene Arten von Verbrauchern durch das Modul erkannt und auf den Verbrauchertyp
angepasste Aktionen angeboten .
<br> <br>
<ul>
<table>
<colgroup> < col width = 25 % > < col width = 75 % > < / colgroup >
<tr> <td> <b> SMA Bluetoth Steckdosen </b> </td> <td> <b> on </b> einschalten , <b> off </b> ausschalten , <b> auto </b> Steuerung durch den Sunny Home Manager </td> </tr>
<tr> <td> <b> SMA EV Charger </b> </td> <td> <b> GC </b> Anteil des Netzbezugs in Prozent , der für das Laden des E - Fahrzeugs akzeptiert werden soll ( 0 .. 100 ) </td> </tr>
</table>
</ul>
2020-06-20 08:02:04 +00:00
</li>
2019-06-12 21:29:10 +00:00
</ul>
2019-03-03 20:08:04 +00:00
</ul>
<br> <br>
< a name = "SMAPortalGet" > </a>
<b> Get </b>
<ul>
<br>
<ul>
2020-06-20 08:02:04 +00:00
< a name = "data" > </a>
2020-08-06 20:39:49 +00:00
<li> <b> data </b> <br>
2019-06-21 21:24:31 +00:00
Mit diesem Befehl werden die Daten aus dem SMA Sunny Portal manuell abgerufen .
2019-03-03 20:08:04 +00:00
</ul>
2020-06-20 08:02:04 +00:00
</li>
2019-03-03 20:08:04 +00:00
<br>
<ul>
2020-06-20 08:02:04 +00:00
< a name = "storedCredentials" > </a>
2020-08-06 20:39:49 +00:00
<li> <b> storedCredentials </b> <br>
2019-03-03 20:08:04 +00:00
Die gespeicherten Anmeldeinformationen ( Credentials ) werden in einem Popup als Klartext angezeigt .
</ul>
2020-06-20 08:02:04 +00:00
</li>
2019-03-03 20:08:04 +00:00
</ul>
<br> <br>
< a name = "SMAPortalAttr" > </a>
<b> Attribute </b>
<ul>
<br>
2020-08-06 19:39:34 +00:00
<ul>
< a name = "balanceDay" > </a>
2020-10-11 07:40:33 +00:00
<li> <b> balanceDay & lt ; YYYY - MM - DD & gt ; [ current current - x & lt ; YYYY - MM - DD & gt ; & lt ; YYYY - MM - DD & gt ; ... ] </b> <br>
Legt fest , von welchen Tagen der Datenprovider "balanceDayData" die Daten liefert .
In der Relativangabe <b> current - x </b> ist <b> x </b> die Anzahl Tage die vom aktuellen Tag subtrahiert werden .
Die Tagesangaben werden durch Leerzeichen getrennt , current = aktueller Tag . <br>
2020-08-08 10:29:35 +00:00
( default: aktueller Tag ) <br> <br>
<ul>
2020-10-11 07:40:33 +00:00
<b> Beispiele: </b> <br>
attr & lt ; name & gt ; balanceDay current 2020 - 08 - 07 2020 - 08 - 06 2020 - 08 - 05 <br>
attr & lt ; name & gt ; balanceDay current current - 1 current - 2 <br>
2020-08-08 10:29:35 +00:00
</ul>
2020-08-06 19:39:34 +00:00
</li> <br>
< a name = "balanceMonth" > </a>
2020-10-11 07:40:33 +00:00
<li> <b> balanceMonth & lt ; YYYY - MM & gt ; [ current current - x & lt ; YYYY - MM & gt ; & lt ; YYYY - MM & gt ; ... ] </b> <br>
Legt fest , von welchen Monaten der Datenprovider "balanceMonthData" die Daten liefert .
In der Relativangabe <b> current - x </b> ist <b> x </b> die Anzahl Monate die vom aktuellen Monat subtrahiert werden .
Die Monatsangaben werden durch Leerzeichen getrennt , current = aktueller Monat . <br>
2020-08-08 10:29:35 +00:00
( default: aktueller Monat ) <br> <br>
<ul>
2020-10-11 07:40:33 +00:00
<b> Beispiele: </b> <br>
attr & lt ; name & gt ; balanceMonth current 2019 - 07 2019 - 06 2019 - 05 <br>
attr & lt ; name & gt ; balanceMonth current current - 12 current - 24 <br>
2020-08-08 10:29:35 +00:00
</ul>
2020-08-06 19:39:34 +00:00
</li> <br>
< a name = "balanceYear" > </a>
2020-10-11 07:40:33 +00:00
<li> <b> balanceYear & lt ; YYYY & gt ; [ current current - x & lt ; YYYY & gt ; & lt ; YYYY & gt ; & lt ; YYYY & gt ; ... ] </b> <br>
Legt fest , von welchen Jahren der Datenprovider "balanceYearData" die Daten liefert .
In der Relativangabe <b> current - x </b> ist <b> x </b> die Anzahl Jahre die vom aktuellen Jahr subtrahiert werden .
Die Jahresangaben werden durch Leerzeichen getrennt , current = aktuelles Jahr . <br>
2020-08-08 10:29:35 +00:00
( default: aktuelles Jahr ) <br> <br>
<ul>
2020-10-11 07:40:33 +00:00
<b> Beispiele: </b> <br>
attr & lt ; name & gt ; balanceYear current 2019 2018 2017 <br>
attr & lt ; name & gt ; balanceYear current current - 1 current - 2 <br>
2020-08-08 10:29:35 +00:00
</ul>
2020-08-06 19:39:34 +00:00
</li> <br>
2020-09-30 18:35:10 +00:00
< a name = "cookieDelete" > </a>
<li> <b> cookieDelete </b> <br>
Legt die Methode der Cookie Verwaltung ( Löschung ) fest . <br>
( default: auto )
<br>
<ul>
<table>
<colgroup> < col width = 5 % > < col width = 95 % > < / colgroup >
2020-10-10 19:05:38 +00:00
<tr> <td> <b> auto </b> </td> <td> - Cookie File wird nach einem internen Verfahren verwaltet </td> </tr>
<tr> <td> <b> afterAttempt </b> </td> <td> - Cookie File wird nach jedem fehlerhaften Leseversuch gelöscht </td> </tr>
<tr> <td> <b> afterCycle </b> </td> <td> - Cookie File wird nach jedem Lesezyklus gelöscht ( umfasst mehrere Versuche ) </td> </tr>
<tr> <td> <b> afterRun </b> </td> <td> - Cookie File wird nach jedem Durchlauf eines Datenabrufs gelöscht </td> </tr>
<tr> <td> <b> afterAttempt & Run </b> </td> <td> - Cookie File wird nach jedem fehlerhaften Leseversuch <b> und </b> Durchlauf eines Datenabrufs gelöscht </td> </tr>
2020-09-30 18:35:10 +00:00
</table>
</ul>
</li> <br>
2019-03-03 20:08:04 +00:00
< a name = "cookieLocation" > </a>
<li> <b> cookieLocation & lt ; Pfad /File> </ b > <br>
2020-05-28 11:40:33 +00:00
Angabe von Pfad und Datei zur Abspeicherung des empfangenen Cookies . <br>
( default: . /log/ & lt ; name & gt ; _cookie . txt )
2019-03-03 20:08:04 +00:00
<br> <br>
<ul>
2020-04-20 14:55:36 +00:00
<b> Beispiel: </b> <br>
2019-03-03 20:08:04 +00:00
attr & lt ; name & gt ; cookieLocation . /log/coo kies . txt <br>
</ul>
</li> <br>
< a name = "disable" > </a>
<li> <b> disable </b> <br>
2020-07-03 07:17:50 +00:00
Deaktiviert das Device .
</li> <br>
2019-03-09 08:32:50 +00:00
2019-03-03 20:08:04 +00:00
< a name = "interval" > </a>
<li> <b> interval & lt ; Sekunden & gt ; </b> <br>
2019-06-21 21:24:31 +00:00
Zeitintervall zum kontinuierlichen Datenabruf aus dem SMA Sunny Portal ( Default: 300 Sekunden ) . <br>
2020-04-20 14:55:36 +00:00
Ist interval explizit auf "0" gesetzt , erfolgt kein automatischer Datenabruf und muss mit "get <name> data" manuell
erfolgen . <br> <br>
2019-05-30 07:44:31 +00:00
2020-06-20 08:02:04 +00:00
<ul>
<b> Hinweis: </b>
2020-11-11 16:34:09 +00:00
Das Abfrageintervall darf nicht kleiner 180 Sekunden sein . Nach bisherigen Erfahrungen toleriert SMA ein
Intervall von 180 Sekunden obwohl lt . SMA AGB der automatische Datenabruf untersagt ist .
2020-06-20 08:02:04 +00:00
</ul>
</li> <br>
2020-07-03 07:17:50 +00:00
< a name = "noHomeManager" > </a>
<li> <b> noHomeManager </b> <br>
Muss gesetzt werden wenn kein Sunny Home Manager installiert ist .
</li> <br>
2020-06-20 08:02:04 +00:00
< a name = "plantLogbookApprovalState" > </a>
<li> <b> plantLogbookApprovalState </b> <br>
Mit diesem Attribut werden die Einträge entsprechend ihres Status gefiltert . <br>
( default: Any )
<br>
<ul>
<table>
<colgroup> < col width = 5 % > < col width = 95 % > < / colgroup >
<tr> <td> <b> Any </b> </td> <td> - alle Mitteilungen </td> </tr>
<tr> <td> <b> NotApproved </b> </td> <td> - nicht bestätigte Mitteilungen </td> </tr>
</table>
</ul>
</li> <br>
< a name = "plantLogbookTypes" > </a>
<li> <b> plantLogbookTypes </b> <br>
Mit diesem Attribut werden die zu selektierenden Mitteilungstypen des Anlagenlogbuchs festgelegt .
Es werden maximal die aktuellsten 25 Logbucheinträge aller eingestellten Typen angezeigt . <br>
( default: Warning , Disturbance , Error )
</li> <br>
< a name = "providerLevel" > </a>
<li> <b> providerLevel </b> <br>
Es wird der Umfang der zu generierenden Daten eingestellt . Anlagen - und Verbraucherstammdaten werden immer abgerufen .
<br> <br>
<ul>
<table>
<colgroup> < col width = 5 % > < col width = 95 % > < / colgroup >
<tr> <td> <b> liveData </b> </td> <td> - erzeugt Readings der aktuellen Erzeugungs - und Verbrauchsdaten </td> </tr>
<tr> <td> <b> weatherData </b> </td> <td> - von SMA angebotene Wetterdaten werden abgerufen </td> </tr>
2020-07-03 07:17:50 +00:00
<tr> <td> <b> forecastData </b> </td> <td> - Vorhersagedaten der Erzeugung / Verbrauch und Verbraucherplanungsdaten werden erzeugt (ein SMA Home Manager muss vorhanden sein) </ td > </tr>
2020-06-20 08:02:04 +00:00
<tr> <td> <b> consumerCurrentdata </b> </td> <td> - aktuelle Verbraucherdaten werden erzeugt </td> </tr>
<tr> <td> <b> consumerDayData </b> </td> <td> - Verbraucherdaten Tag werden erzeugt </td> </tr>
<tr> <td> <b> consumerMonthData </b> </td> <td> - Verbraucherdaten Monat werden erzeugt </td> </tr>
<tr> <td> <b> consumerYearData </b> </td> <td> - Verbraucherdaten Jahr werden erzeugt </td> </tr>
<tr> <td> <b> plantLogbook </b> </td> <td> - die maximal 25 aktuellsten Einträge des Anlagenlogbuchs werden abgerufen </td> </tr>
2020-08-06 19:39:34 +00:00
<tr> <td> <b> balanceDayData </b> </td> <td> - Statistikdaten des Tages werden abgerufen ( siehe Attribut < a href = "#balanceDay" > balanceDay </a> ) </td> </tr>
<tr> <td> <b> balanceMonthData </b> </td> <td> - Statistikdaten des Monats werden abgerufen ( siehe Attribut < a href = "#balanceMonth" > balanceMonth </a> ) </td> </tr>
<tr> <td> <b> balanceYearData </b> </td> <td> - Statistikdaten des Jahres werden abgerufen ( siehe Attribut < a href = "#balanceYear" > balanceYear </a> ) </td> </tr>
2020-07-02 19:45:08 +00:00
<tr> <td> <b> balanceTotalData </b> </td> <td> - Statistikdaten Gesamt werden abgerufen </td> </tr>
2020-06-20 08:02:04 +00:00
</table>
</ul>
<br>
2019-05-30 07:44:31 +00:00
</li> <br>
2019-03-03 20:08:04 +00:00
< a name = "showPassInLog" > </a>
<li> <b> showPassInLog </b> <br>
2020-06-20 08:02:04 +00:00
Wenn gesetzt , wird das verwendete Passwort im Logfile angezeigt . <br>
( default: 0 )
</li> <br>
2019-03-03 20:08:04 +00:00
< a name = "userAgent" > </a>
<li> <b> userAgent & lt ; Kennung & gt ; </b> <br>
2019-06-21 21:24:31 +00:00
Es kann die User - Agent - Kennung zur Identifikation gegenüber dem SMA Sunny Portal angegeben werden .
2019-03-03 20:08:04 +00:00
<br> <br>
<ul>
2020-04-20 14:55:36 +00:00
<b> Beispiel: </b> <br>
2019-03-03 20:08:04 +00:00
attr & lt ; name & gt ; userAgent Mozilla /5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/ 20100101 Firefox / 65.0 <br>
2020-06-20 08:02:04 +00:00
</ul>
2019-06-10 16:32:58 +00:00
</li> <br>
2020-10-11 19:25:54 +00:00
< a name = "useRelativeNames" > </a>
<li> <b> useRelativeNames </b> <br>
Bei Verwendung von relativen Datumangaben <b> current - x </b> ( siehe balance . * - Attribute ) enthält der erstellte Readingname
ebenfalls die relative anstatt der realen Datumangabe . <br>
( default: reale Datumangabe )
</li> <br>
2019-06-10 16:32:58 +00:00
< a name = "verbose5Data" > </a>
<li> <b> verbose5Data </b> <br>
2020-06-11 20:30:49 +00:00
Mit dem Attributwert verbose 5 werden sehr große Datenmengen generiert .
Es können gezielt die interessierenden verbose 5 Ausgaben selektiert werden . <br>
( default: none )
2019-03-03 20:08:04 +00:00
</li> <br>
2019-05-30 07:44:31 +00:00
</ul>
</ul>
2019-06-03 23:00:53 +00:00
<br>
2019-03-03 20:08:04 +00:00
2019-03-12 22:17:06 +00:00
</ul>
= end html_DE
= for : application / json ; q = META . json 76 _SMAPortal . pm
{
2019-05-30 07:44:31 +00:00
"abstract" : "Module for communication with the SMA Sunny Portal" ,
2019-03-12 22:17:06 +00:00
"x_lang" : {
"de" : {
2019-05-30 07:44:31 +00:00
"abstract" : "Modul zur Kommunikation mit dem SMA Sunny Portal"
2019-03-12 22:17:06 +00:00
}
} ,
"keywords" : [
"sma" ,
"photovoltaik" ,
"electricity" ,
"portal" ,
"smaportal"
] ,
2019-03-18 23:51:42 +00:00
"version" : "v1.1.1" ,
2019-06-21 21:24:31 +00:00
"release_status" : "stable" ,
2019-03-12 22:17:06 +00:00
"author" : [
2019-05-30 07:44:31 +00:00
"Heiko Maaz <heiko.maaz@t-online.de>" ,
"Wzut" ,
"XGuide" ,
null
2019-03-12 22:17:06 +00:00
] ,
"x_fhem_maintainer" : [
"DS_Starter"
] ,
"x_fhem_maintainer_github" : [
"nasseeder1"
] ,
"prereqs" : {
"runtime" : {
"requires" : {
"FHEM" : 5.00918799 ,
"perl" : 5.014 ,
"JSON" : 0 ,
2019-03-24 15:51:33 +00:00
"Encode" : 0 ,
2019-03-12 22:17:06 +00:00
"POSIX" : 0 ,
"Data::Dumper" : 0 ,
2019-03-16 06:48:59 +00:00
"Blocking" : 0 ,
"GPUtils" : 0 ,
"Time::HiRes" : 0 ,
2019-03-24 15:51:33 +00:00
"Time::Local" : 0 ,
2019-03-16 06:48:59 +00:00
"LWP" : 0 ,
"HTTP::Cookies" : 0 ,
2020-06-20 08:02:04 +00:00
"MIME::Base64" : 0 ,
2020-11-18 14:07:45 +00:00
"utf8" : 0
2019-03-12 22:17:06 +00:00
} ,
"recommends" : {
2019-03-25 19:19:05 +00:00
"FHEM::Meta" : 0
2019-03-12 22:17:06 +00:00
} ,
"suggests" : {
}
}
}
}
= end : application / json ; q = META . json
= cut