2020-12-13 17:29:15 +00:00
########################################################################################################################
# $Id: 76_SolarForecast.pm 21735 2020-04-20 20:53:24Z DS_Starter $
#########################################################################################################################
# 76_SolarForecast.pm
#
2021-01-17 19:13:02 +00:00
# (c) 2020-2021 by Heiko Maaz e-mail: Heiko dot Maaz at t-online dot de
2020-12-13 17:29:15 +00:00
#
# This Module is used by module 76_SMAPortal to create graphic devices.
# It can't be used standalone without any SMAPortal-Device.
#
# 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/>.
#
#########################################################################################################################
package FHEM::SolarForecast ; ## no critic 'package'
use strict ;
use warnings ;
use POSIX ;
use GPUtils qw( GP_Import GP_Export ) ; # wird für den Import der FHEM Funktionen aus der fhem.pl benötigt
use Time::HiRes qw( gettimeofday ) ;
eval "use FHEM::Meta;1" or my $ modMetaAbsent = 1 ; ## no critic 'eval'
use Encode ;
use utf8 ;
2021-01-17 19:13:02 +00:00
eval "use JSON;1;" or my $ jsonabs = "JSON" ; ## no critic 'eval' # Debian: apt-get install libjson-perl
2020-12-13 17:29:15 +00:00
2021-01-17 19:13:02 +00:00
use FHEM::SynoModules::SMUtils qw( evaljson
moduleVersion
2021-01-23 11:46:54 +00:00
trim
2021-01-17 19:13:02 +00:00
) ; # Hilfsroutinen Modul
2021-01-26 20:38:22 +00:00
use Data::Dumper ;
no if $] >= 5.017011 , warnings = > 'experimental::smartmatch' ;
2021-01-17 19:13:02 +00:00
2020-12-13 17:29:15 +00:00
# Run before module compilation
BEGIN {
# Import from main::
GP_Import (
qw(
2021-01-17 19:13:02 +00:00
attr
2020-12-13 17:29:15 +00:00
AnalyzePerlCommand
AttrVal
AttrNum
2021-01-17 19:13:02 +00:00
data
2020-12-13 17:29:15 +00:00
defs
delFromDevAttrList
delFromAttrList
devspec2array
deviceEvents
Debug
2020-12-13 21:48:45 +00:00
fhemTimeLocal
2020-12-13 17:29:15 +00:00
FmtDateTime
2021-01-17 19:13:02 +00:00
FileWrite
FileRead
FileDelete
2020-12-13 17:29:15 +00:00
FmtTime
FW_makeImage
getKeyValue
init_done
InternalTimer
IsDisabled
Log
Log3
2020-12-15 13:41:10 +00:00
modules
parseParams
2020-12-13 17:29:15 +00:00
readingsSingleUpdate
readingsBulkUpdate
readingsBulkUpdateIfChanged
readingsBeginUpdate
readingsDelete
readingsEndUpdate
ReadingsNum
ReadingsTimestamp
ReadingsVal
RemoveInternalTimer
readingFnAttributes
setKeyValue
sortTopicNum
FW_cmd
FW_directNotify
FW_ME
FW_subdir
FW_room
FW_detail
FW_wname
)
) ;
# Export to main context with different name
# my $pkg = caller(0);
# my $main = $pkg;
# $main =~ s/^(?:.+::)?([^:]+)$/main::$1\_/g;
# foreach (@_) {
# *{ $main . $_ } = *{ $pkg . '::' . $_ };
# }
GP_Export (
qw(
Initialize
pageAsHtml
)
) ;
}
# Versions History intern
my % vNotesIntern = (
2021-03-21 11:50:35 +00:00
"0.17.1" = > "21.03.2021 bug fixes " ,
2021-03-20 14:14:18 +00:00
"0.17.0" = > "20.03.2021 new attr cloudFactorSlope / rainFactorSlope, fixes in Graphic sub " ,
2021-03-20 11:36:59 +00:00
"0.16.0" = > "19.03.2021 new getter nextHours, some fixes " ,
2021-03-19 18:06:34 +00:00
"0.15.3" = > "19.03.2021 corrected weather consideration for call calcPVforecast " ,
2021-03-19 13:17:55 +00:00
"0.15.2" = > "19.03.2021 some bug fixing " ,
2021-03-18 22:12:08 +00:00
"0.15.1" = > "18.03.2021 replace ThisHour_ by NextHour00_ " ,
2021-03-18 16:32:44 +00:00
"0.15.0" = > "18.03.2021 delete overhanging readings in sub _transferDWDForecastValues " ,
"0.14.0" = > "17.03.2021 new getter PVReal, weatherData, consumption total in currentMeterdev " ,
2021-03-16 14:53:54 +00:00
"0.13.0" = > "16.03.2021 changed sub forecastGraphic from Wzut " ,
"0.12.0" = > "16.03.2021 switch etoday to etotal " ,
2021-03-14 11:51:38 +00:00
"0.11.0" = > "14.03.2021 new attr history_hour, beam1Content, beam2Content, implement sub forecastGraphic from Wzut, " .
2021-03-14 15:06:33 +00:00
"rename attr beamColor, beamColor2 , more fixes " ,
2021-03-14 11:51:38 +00:00
"0.10.0" = > "13.03.2021 hour shifter in sub _transferMeterValues, lot of fixes " ,
2021-03-13 15:50:33 +00:00
"0.9.0" = > "13.03.2021 more helper hashes Forum: https://forum.fhem.de/index.php/topic,117864.msg1139251.html#msg1139251 " .
"cachefile pvhist is persistent " ,
2021-03-13 12:25:19 +00:00
"0.8.0" = > "07.03.2021 helper hash Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350 " ,
2021-03-01 21:18:23 +00:00
"0.7.0" = > "01.03.2021 add function DbLog_splitFn " ,
2021-01-27 17:02:18 +00:00
"0.6.0" = > "27.01.2021 change calcPVforecast from formula 1 to formula 2 " ,
2021-01-26 20:38:22 +00:00
"0.5.0" = > "25.01.2021 add multistring support, add reset inverterStrings " ,
2021-01-24 18:03:44 +00:00
"0.4.0" = > "24.01.2021 setter moduleDirection, add Area factor to calcPVforecast, add reset pvCorrection " ,
"0.3.0" = > "21.01.2021 add cloud correction, add rain correction, add reset pvHistory, setter writeHistory " ,
"0.2.0" = > "20.01.2021 use SMUtils, JSON, implement getter data,html,pvHistory, correct the 'disable' problem " ,
2020-12-13 17:29:15 +00:00
"0.1.0" = > "09.12.2020 initial Version "
) ;
# Voreinstellungen
my % hset = ( # Hash der Set-Funktion
2020-12-27 18:51:53 +00:00
currentForecastDev = > { fn = > \ & _setcurrentForecastDev } ,
2021-01-27 17:02:18 +00:00
modulePeakString = > { fn = > \ & _setmodulePeakString } ,
2021-01-26 20:38:22 +00:00
inverterStrings = > { fn = > \ & _setinverterStrings } ,
2020-12-27 18:51:53 +00:00
currentInverterDev = > { fn = > \ & _setinverterDevice } ,
currentMeterDev = > { fn = > \ & _setmeterDevice } ,
2020-12-15 13:41:10 +00:00
pvCorrectionFactor_05 = > { fn = > \ & _setpvCorrectionFactor } ,
pvCorrectionFactor_06 = > { fn = > \ & _setpvCorrectionFactor } ,
pvCorrectionFactor_07 = > { fn = > \ & _setpvCorrectionFactor } ,
pvCorrectionFactor_08 = > { fn = > \ & _setpvCorrectionFactor } ,
pvCorrectionFactor_09 = > { fn = > \ & _setpvCorrectionFactor } ,
pvCorrectionFactor_10 = > { fn = > \ & _setpvCorrectionFactor } ,
pvCorrectionFactor_11 = > { fn = > \ & _setpvCorrectionFactor } ,
pvCorrectionFactor_12 = > { fn = > \ & _setpvCorrectionFactor } ,
pvCorrectionFactor_13 = > { fn = > \ & _setpvCorrectionFactor } ,
pvCorrectionFactor_14 = > { fn = > \ & _setpvCorrectionFactor } ,
pvCorrectionFactor_15 = > { fn = > \ & _setpvCorrectionFactor } ,
pvCorrectionFactor_16 = > { fn = > \ & _setpvCorrectionFactor } ,
pvCorrectionFactor_17 = > { fn = > \ & _setpvCorrectionFactor } ,
pvCorrectionFactor_18 = > { fn = > \ & _setpvCorrectionFactor } ,
pvCorrectionFactor_19 = > { fn = > \ & _setpvCorrectionFactor } ,
pvCorrectionFactor_20 = > { fn = > \ & _setpvCorrectionFactor } ,
pvCorrectionFactor_21 = > { fn = > \ & _setpvCorrectionFactor } ,
pvCorrectionFactor_Auto = > { fn = > \ & _setpvCorrectionFactorAuto } ,
2020-12-27 18:51:53 +00:00
reset = > { fn = > \ & _setreset } ,
2020-12-16 19:15:48 +00:00
moduleTiltAngle = > { fn = > \ & _setmoduleTiltAngle } ,
2021-01-24 18:03:44 +00:00
moduleDirection = > { fn = > \ & _setmoduleDirection } ,
2021-01-23 21:31:11 +00:00
writeHistory = > { fn = > \ & _setwriteHistory } ,
2020-12-16 19:15:48 +00:00
) ;
2021-01-17 19:13:02 +00:00
my % hget = ( # Hash für Get-Funktion (needcred => 1: Funktion benötigt gesetzte Credentials)
2021-03-18 17:25:37 +00:00
data = > { fn = > \ & _getdata , needcred = > 0 } ,
html = > { fn = > \ & _gethtml , needcred = > 0 } ,
ftui = > { fn = > \ & _getftui , needcred = > 0 } ,
pvHistory = > { fn = > \ & _getlistPVHistory , needcred = > 0 } ,
pvReal = > { fn = > \ & _getlistPVReal , needcred = > 0 } ,
pvForecast = > { fn = > \ & _getlistPVForecast , needcred = > 0 } ,
2021-03-20 09:05:26 +00:00
nextHours = > { fn = > \ & _getlistNextHours , needcred = > 0 } ,
2021-03-18 17:25:37 +00:00
weatherData = > { fn = > \ & _getlistWeather , needcred = > 0 } ,
stringConfig = > { fn = > \ & _getstringConfig , needcred = > 0 } ,
2021-01-17 19:13:02 +00:00
) ;
2021-01-24 18:03:44 +00:00
my % hff = ( # Flächenfaktoren
"0" = > { N = > 100 , NE = > 100 , E = > 100 , SE = > 100 , S = > 100 , SW = > 100 , W = > 100 , NW = > 100 } , # http://www.ing-büro-junge.de/html/photovoltaik.html
"10" = > { N = > 90 , NE = > 93 , E = > 100 , SE = > 105 , S = > 107 , SW = > 105 , W = > 100 , NW = > 93 } ,
"20" = > { N = > 80 , NE = > 84 , E = > 97 , SE = > 109 , S = > 114 , SW = > 109 , W = > 97 , NW = > 84 } ,
"30" = > { N = > 69 , NE = > 76 , E = > 94 , SE = > 110 , S = > 116 , SW = > 110 , W = > 94 , NW = > 76 } ,
"40" = > { N = > 59 , NE = > 68 , E = > 90 , SE = > 109 , S = > 117 , SW = > 109 , W = > 90 , NW = > 68 } ,
"45" = > { N = > 55 , NE = > 65 , E = > 87 , SE = > 108 , S = > 115 , SW = > 108 , W = > 87 , NW = > 65 } ,
"50" = > { N = > 49 , NE = > 62 , E = > 85 , SE = > 107 , S = > 113 , SW = > 107 , W = > 85 , NW = > 62 } ,
"60" = > { N = > 42 , NE = > 55 , E = > 80 , SE = > 102 , S = > 111 , SW = > 102 , W = > 80 , NW = > 55 } ,
"70" = > { N = > 37 , NE = > 50 , E = > 74 , SE = > 95 , S = > 104 , SW = > 95 , W = > 74 , NW = > 50 } ,
"80" = > { N = > 35 , NE = > 46 , E = > 67 , SE = > 86 , S = > 95 , SW = > 86 , W = > 67 , NW = > 46 } ,
"90" = > { N = > 33 , NE = > 43 , E = > 62 , SE = > 78 , S = > 85 , SW = > 78 , W = > 62 , NW = > 43 } ,
2020-12-13 17:29:15 +00:00
) ;
2021-01-04 20:25:02 +00:00
my % weather_ids = (
# s => 0 , 0 - 3 DWD -> kein signifikantes Wetter
# s => 1 , 45 - 99 DWD -> signifikantes Wetter
2021-03-20 09:05:26 +00:00
'0' = > { s = > '0' , icon = > 'weather_sun' , txtd = > 'sonnig' } ,
'1' = > { s = > '0' , icon = > 'weather_cloudy_light' , txtd = > 'Bewölkung abnehmend' } ,
'2' = > { s = > '0' , icon = > 'weather_cloudy' , txtd = > 'Bewölkung unverändert' } ,
'3' = > { s = > '0' , icon = > 'weather_cloudy_heavy' , txtd = > 'Bewölkung zunehmend' } ,
'4' = > { s = > '0' , icon = > 'unknown' , txtd = > 'Sicht durch Rauch oder Asche vermindert' } ,
'5' = > { s = > '0' , icon = > 'unknown' , txtd = > 'trockener Dunst (relative Feuchte < 80 %)' } ,
'6' = > { s = > '0' , icon = > 'unknown' , txtd = > 'verbreiteter Schwebstaub, nicht vom Wind herangeführt' } ,
'7' = > { s = > '0' , icon = > 'unknown' , txtd = > 'Staub oder Sand bzw. Gischt, vom Wind herangeführt' } ,
'8' = > { s = > '0' , icon = > 'unknown' , txtd = > 'gut entwickelte Staub- oder Sandwirbel' } ,
'9' = > { s = > '0' , icon = > 'unknown' , txtd = > 'Staub- oder Sandsturm im Gesichtskreis, aber nicht an der Station' } ,
'10' = > { s = > '0' , icon = > 'weather_fog' , txtd = > 'Nebel' } ,
'11' = > { s = > '0' , icon = > 'weather_rain_fog' , txtd = > 'Nebel mit Regen' } ,
'12' = > { s = > '0' , icon = > 'weather_fog' , txtd = > 'durchgehender Bodennebel' } ,
'13' = > { s = > '0' , icon = > 'unknown' , txtd = > 'Wetterleuchten sichtbar, kein Donner gehört' } ,
'14' = > { s = > '0' , icon = > 'unknown' , txtd = > 'Niederschlag im Gesichtskreis, nicht den Boden erreichend' } ,
'15' = > { s = > '0' , icon = > 'unknown' , txtd = > 'Niederschlag in der Ferne (> 5 km), aber nicht an der Station' } ,
'16' = > { s = > '0' , icon = > 'unknown' , txtd = > 'Niederschlag in der Nähe (< 5 km), aber nicht an der Station' } ,
'17' = > { s = > '0' , icon = > 'unknown' , txtd = > 'Gewitter (Donner hörbar), aber kein Niederschlag an der Station' } ,
'18' = > { s = > '0' , icon = > 'unknown' , txtd = > 'Markante Böen im Gesichtskreis, aber kein Niederschlag an der Station' } ,
'19' = > { s = > '0' , icon = > 'unknown' , txtd = > 'Tromben (trichterförmige Wolkenschläuche) im Gesichtskreis' } ,
'20' = > { s = > '0' , icon = > 'unknown' , txtd = > 'nach Sprühregen oder Schneegriesel' } ,
'21' = > { s = > '0' , icon = > 'unknown' , txtd = > 'nach Regen' } ,
'22' = > { s = > '0' , icon = > 'unknown' , txtd = > 'nach Schnefall' } ,
'23' = > { s = > '0' , icon = > 'unknown' , txtd = > 'nach Schneeregen oder Eiskörnern' } ,
'24' = > { s = > '0' , icon = > 'unknown' , txtd = > 'nach gefrierendem Regen' } ,
'25' = > { s = > '0' , icon = > 'unknown' , txtd = > 'nach Regenschauer' } ,
'26' = > { s = > '0' , icon = > 'unknown' , txtd = > 'nach Schneeschauer' } ,
'27' = > { s = > '0' , icon = > 'unknown' , txtd = > 'nach Graupel- oder Hagelschauer' } ,
'28' = > { s = > '0' , icon = > 'unknown' , txtd = > 'nach Nebel' } ,
'29' = > { s = > '0' , icon = > 'unknown' , txtd = > 'nach Gewitter' } ,
'30' = > { s = > '0' , icon = > 'unknown' , txtd = > 'leichter oder mäßiger Sandsturm, an Intensität abnehmend' } ,
'31' = > { s = > '0' , icon = > 'unknown' , txtd = > 'leichter oder mäßiger Sandsturm, unveränderte Intensität' } ,
'32' = > { s = > '0' , icon = > 'unknown' , txtd = > 'leichter oder mäßiger Sandsturm, an Intensität zunehmend' } ,
'33' = > { s = > '0' , icon = > 'unknown' , txtd = > 'schwerer Sandsturm, an Intensität abnehmend' } ,
'34' = > { s = > '0' , icon = > 'unknown' , txtd = > 'schwerer Sandsturm, unveränderte Intensität' } ,
'35' = > { s = > '0' , icon = > 'unknown' , txtd = > 'schwerer Sandsturm, an Intensität zunehmend' } ,
'36' = > { s = > '0' , icon = > 'weather_snow_light' , txtd = > 'leichtes oder mäßiges Schneefegen, unter Augenhöhe' } ,
'37' = > { s = > '0' , icon = > 'weather_snow_heavy' , txtd = > 'starkes Schneefegen, unter Augenhöhe' } ,
'38' = > { s = > '0' , icon = > 'weather_snow_light' , txtd = > 'leichtes oder mäßiges Schneetreiben, über Augenhöhe' } ,
'39' = > { s = > '0' , icon = > 'weather_snow_heavy' , txtd = > 'starkes Schneetreiben, über Augenhöhe' } ,
'40' = > { s = > '0' , icon = > 'weather_fog' , txtd = > 'Nebel in einiger Entfernung' } ,
'41' = > { s = > '0' , icon = > 'weather_fog' , txtd = > 'Nebel in Schwaden oder Bänken' } ,
'42' = > { s = > '0' , icon = > 'weather_fog' , txtd = > 'Nebel, Himmel erkennbar, dünner werdend' } ,
'43' = > { s = > '0' , icon = > 'weather_fog' , txtd = > 'Nebel, Himmel nicht erkennbar, dünner werdend' } ,
'44' = > { s = > '0' , icon = > 'weather_fog' , txtd = > 'Nebel, Himmel erkennbar, unverändert' } ,
'45' = > { s = > '1' , icon = > 'weather_fog' , txtd = > 'Nebel' } ,
'46' = > { s = > '0' , icon = > 'weather_fog' , txtd = > 'Nebel, Himmel erkennbar, dichter werdend' } ,
'47' = > { s = > '0' , icon = > 'weather_fog' , txtd = > 'Nebel, Himmel nicht erkennbar, dichter werdend' } ,
'48' = > { s = > '1' , icon = > 'weather_fog' , txtd = > 'Nebel mit Reifbildung' } ,
'49' = > { s = > '0' , icon = > 'weather_fog' , txtd = > 'Nebel mit Reifansatz, Himmel nicht erkennbar' } ,
'50' = > { s = > '0' , icon = > 'weather_rain' , txtd = > 'unterbrochener leichter Sprühregen' } ,
'51' = > { s = > '1' , icon = > 'weather_rain_light' , txtd = > 'leichter Sprühregen' } ,
'52' = > { s = > '0' , icon = > 'weather_rain' , txtd = > 'unterbrochener mäßiger Sprühregen' } ,
'53' = > { s = > '1' , icon = > 'weather_rain_light' , txtd = > 'leichter Sprühregen' } ,
'54' = > { s = > '0' , icon = > 'weather_rain_heavy' , txtd = > 'unterbrochener starker Sprühregen' } ,
'55' = > { s = > '1' , icon = > 'weather_rain_heavy' , txtd = > 'starker Sprühregen' } ,
'56' = > { s = > '1' , icon = > 'weather_rain_light' , txtd = > 'leichter gefrierender Sprühregen' } ,
'57' = > { s = > '1' , icon = > 'weather_rain_heavy' , txtd = > 'mäßiger oder starker gefrierender Sprühregen' } ,
'58' = > { s = > '0' , icon = > 'weather_rain_light' , txtd = > 'leichter Sprühregen mit Regen' } ,
'59' = > { s = > '0' , icon = > 'weather_rain_heavy' , txtd = > 'mäßiger oder starker Sprühregen mit Regen' } ,
'60' = > { s = > '0' , icon = > 'weather_rain_light' , txtd = > 'unterbrochener leichter Regen oder einzelne Regentropfen' } ,
'61' = > { s = > '1' , icon = > 'weather_rain_light' , txtd = > 'leichter Regen' } ,
'62' = > { s = > '0' , icon = > 'weather_rain' , txtd = > 'unterbrochener mäßiger Regen' } ,
'63' = > { s = > '1' , icon = > 'weather_rain' , txtd = > 'mäßiger Regen' } ,
'64' = > { s = > '0' , icon = > 'weather_rain_heavy' , txtd = > 'unterbrochener starker Regen' } ,
'65' = > { s = > '1' , icon = > 'weather_rain_heavy' , txtd = > 'starker Regen' } ,
'66' = > { s = > '1' , icon = > 'weather_rain_snow_light' , txtd = > 'leichter gefrierender Regen' } ,
'67' = > { s = > '1' , icon = > 'weather_rain_snow_heavy' , txtd = > 'mäßiger oder starker gefrierender Regen' } ,
'68' = > { s = > '0' , icon = > 'weather_rain_snow_light' , txtd = > 'leichter Schneeregen' } ,
'69' = > { s = > '0' , icon = > 'weather_rain_snow_heavy' , txtd = > 'mäßiger oder starker Schneeregen' } ,
'70' = > { s = > '0' , icon = > 'weather_snow_light' , txtd = > 'unterbrochener leichter Schneefall oder einzelne Schneeflocken' } ,
'71' = > { s = > '1' , icon = > 'weather_snow_light' , txtd = > 'leichter Schneefall' } ,
'72' = > { s = > '0' , icon = > 'weather_snow' , txtd = > 'unterbrochener mäßiger Schneefall' } ,
'73' = > { s = > '1' , icon = > 'weather_snow' , txtd = > 'mäßiger Schneefall' } ,
'74' = > { s = > '0' , icon = > 'weather_snow_heavy' , txtd = > 'unterbrochener starker Schneefall' } ,
'75' = > { s = > '1' , icon = > 'weather_snow_heavy' , txtd = > 'starker Schneefall' } ,
'76' = > { s = > '0' , icon = > 'weather_frost' , txtd = > 'Eisnadeln (Polarschnee)' } ,
'77' = > { s = > '1' , icon = > 'weather_frost' , txtd = > 'Schneegriesel' } ,
'78' = > { s = > '0' , icon = > 'weather_frost' , txtd = > 'Schneekristalle' } ,
'79' = > { s = > '0' , icon = > 'weather_frost' , txtd = > 'Eiskörner (gefrorene Regentropfen)' } ,
'80' = > { s = > '1' , icon = > 'weather_rain_light' , txtd = > 'leichter Regenschauer' } ,
'81' = > { s = > '1' , icon = > 'weather_rain' , txtd = > 'mäßiger oder starkerRegenschauer' } ,
'82' = > { s = > '1' , icon = > 'weather_rain_heavy' , txtd = > 'sehr starker Regenschauer' } ,
'83' = > { s = > '0' , icon = > 'weather_snow' , txtd = > 'mäßiger oder starker Schneeregenschauer' } ,
'84' = > { s = > '0' , icon = > 'weather_snow_light' , txtd = > 'leichter Schneeschauer' } ,
'85' = > { s = > '1' , icon = > 'weather_snow_light' , txtd = > 'leichter Schneeschauer' } ,
'86' = > { s = > '1' , icon = > 'weather_snow_heavy' , txtd = > 'mäßiger oder starker Schneeschauer' } ,
'87' = > { s = > '0' , icon = > 'weather_snow_heavy' , txtd = > 'mäßiger oder starker Graupelschauer' } ,
'88' = > { s = > '0' , icon = > 'unknown' , txtd = > 'leichter Hagelschauer' } ,
'89' = > { s = > '0' , icon = > 'unknown' , txtd = > 'mäßiger oder starker Hagelschauer' } ,
'90' = > { s = > '0' , icon = > 'weather_thunderstorm' , txtd = > '' } ,
'91' = > { s = > '0' , icon = > 'weather_storm' , txtd = > '' } ,
'92' = > { s = > '0' , icon = > 'weather_thunderstorm' , txtd = > '' } ,
'93' = > { s = > '0' , icon = > 'weather_thunderstorm' , txtd = > '' } ,
'94' = > { s = > '0' , icon = > 'weather_thunderstorm' , txtd = > '' } ,
'95' = > { s = > '1' , icon = > 'weather_thunderstorm' , txtd = > 'leichtes oder mäßiges Gewitter ohne Graupel oder Hagel' } ,
'96' = > { s = > '1' , icon = > 'weather_storm' , txtd = > 'starkes Gewitter ohne Graupel oder Hagel,Gewitter mit Graupel oder Hagel' } ,
'97' = > { s = > '0' , icon = > 'weather_storm' , txtd = > 'starkes Gewitter mit Regen oder Schnee' } ,
'98' = > { s = > '0' , icon = > 'weather_storm' , txtd = > 'starkes Gewitter mit Sandsturm' } ,
'99' = > { s = > '1' , icon = > 'weather_storm' , txtd = > 'starkes Gewitter mit Graupel oder Hagel' } ,
'100' = > { s = > '0' , icon = > 'weather_night ' , txtd = > 'sternenklarer Himmel' } ,
2021-01-04 20:25:02 +00:00
) ;
2021-01-17 19:13:02 +00:00
my @ chours = ( 5 .. 21 ) ; # Stunden des Tages mit möglichen Korrekturwerten
my $ defpvme = 16.52 ; # default Wirkungsgrad Solarmodule
my $ definve = 98.3 ; # default Wirkungsgrad Wechselrichter
my $ kJtokWh = 0.00027778 ; # Umrechnungsfaktor kJ in kWh
my $ defmaxvar = 0.5 ; # max. Varianz pro Tagesberechnung Autokorrekturfaktor
my $ definterval = 70 ; # Standard Abfrageintervall
2021-03-13 14:48:27 +00:00
2021-01-17 19:13:02 +00:00
my $ pvhcache = $ attr { global } { modpath } . "/FHEM/FhemUtils/PVH_SolarForecast_" ; # Filename-Fragment für PV History (wird mit Devicename ergänzt)
2021-03-13 14:48:27 +00:00
my $ pvrcache = $ attr { global } { modpath } . "/FHEM/FhemUtils/PVR_SolarForecast_" ; # Filename-Fragment für PV Real (wird mit Devicename ergänzt)
2021-03-14 06:53:16 +00:00
my $ calcmaxd = 7 ; # Anzahl Tage (default) für Durchschnittermittlung zur Vorhersagekorrektur
2021-01-23 11:46:54 +00:00
my @ dwdattrmust = qw( Rad1h TTT Neff R101 ww SunUp SunRise SunSet ) ; # Werte die im Attr forecastProperties des DWD_Opendata Devices mindestens gesetzt sein müssen
2021-01-23 21:31:11 +00:00
my $ whistrepeat = 900 ; # Wiederholungsintervall Schreiben historische Daten
2021-01-22 20:16:33 +00:00
2021-03-20 13:55:15 +00:00
my $ clslopedef = 45 ; # Steilheit (%) des Korrekturfaktors bzgl. effektiver Bewölkung, siehe: https://www.energie-experten.org/erneuerbare-energien/photovoltaik/planung/sonnenstunden
2021-01-22 14:18:33 +00:00
my $ cloud_base = 0 ; # Fußpunktverschiebung bzgl. effektiver Bewölkung
2021-01-17 19:13:02 +00:00
2021-03-20 13:55:15 +00:00
my $ rslopedef = 20 ; # Steilheit (%) des Korrekturfaktors bzgl. Niederschlag (R101)
2021-01-22 20:16:33 +00:00
my $ rain_base = 0 ; # Fußpunktverschiebung bzgl. effektiver Bewölkung
2021-03-14 06:53:16 +00:00
my @ consdays = qw( 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 ) ; # Auswahl Anzahl Tage für Attr numHistDays
2021-01-20 21:00:34 +00:00
2021-03-18 10:31:34 +00:00
# Information zu verwendeten internen Datenhashes
# $data{$type}{$name}{pvfc} # PV forecast Ringspeicher
# $data{$type}{$name}{weather} # Weather forecast Ringspeicher
2021-03-18 17:25:37 +00:00
# $data{$type}{$name}{pvreal} # PV real Ringspeicher
2021-03-18 10:31:34 +00:00
# $data{$type}{$name}{current} # current values
# $data{$type}{$name}{pvhist} # historische Werte pvreal, pvforecast, gridconsumtion
2021-03-20 09:05:26 +00:00
# $data{$type}{$name}{nexthours} # NextHours Werte
2021-03-18 10:31:34 +00:00
2020-12-13 17:29:15 +00:00
################################################################
# Init Fn
################################################################
sub Initialize {
my ( $ hash ) = @ _ ;
2021-01-20 21:00:34 +00:00
my $ fwd = join "," , devspec2array ( "TYPE=FHEMWEB:FILTER=STATE=Initialized" ) ;
my $ cda = join "," , @ consdays ;
2020-12-13 17:29:15 +00:00
$ hash - > { DefFn } = \ & Define ;
2021-03-13 17:25:42 +00:00
$ hash - > { UndefFn } = \ & Undef ;
2020-12-13 17:29:15 +00:00
$ hash - > { GetFn } = \ & Get ;
$ hash - > { SetFn } = \ & Set ;
2021-01-17 19:13:02 +00:00
$ hash - > { DeleteFn } = \ & Delete ;
2020-12-13 17:29:15 +00:00
$ hash - > { FW_summaryFn } = \ & FwFn ;
$ hash - > { FW_detailFn } = \ & FwFn ;
2021-01-17 19:13:02 +00:00
$ hash - > { ShutdownFn } = \ & Shutdown ;
2021-03-01 21:18:23 +00:00
$ hash - > { DbLog_splitFn } = \ & DbLogSplit ;
2020-12-13 17:29:15 +00:00
$ hash - > { AttrFn } = \ & Attr ;
$ hash - > { NotifyFn } = \ & Notify ;
$ hash - > { AttrList } = "autoRefresh:selectnumbers,120,0.2,1800,0,log10 " .
"autoRefreshFW:$fwd " .
2021-03-14 11:51:38 +00:00
"beam1Color:colorpicker,RGB " .
"beam1Content:forecast,real,consumption " .
"beam2Color:colorpicker,RGB " .
"beam2Content:forecast,real,consumption " .
2020-12-13 17:29:15 +00:00
"beamHeight " .
"beamWidth " .
# "consumerList ".
# "consumerLegend:none,icon_top,icon_bottom,text_top,text_bottom ".
# "consumerAdviceIcon ".
2021-03-20 13:55:15 +00:00
"cloudFactorSlope:slider,0,1,100 " .
2020-12-13 17:29:15 +00:00
"disable:1,0 " .
"forcePageRefresh:1,0 " .
"headerAlignment:center,left,right " .
"headerDetail:all,co,pv,pvco,statusLink " .
2021-03-14 11:51:38 +00:00
"history_hour:slider,-12,-1,0 " .
2020-12-13 17:29:15 +00:00
"hourCount:slider,4,1,24 " .
"hourStyle " .
"htmlStart " .
"htmlEnd " .
2020-12-15 13:41:10 +00:00
"interval " .
2020-12-27 20:04:17 +00:00
"layoutType:pv,co,pvco,diff " .
"maxVariancePerDay " .
"maxPV " .
2021-01-20 21:00:34 +00:00
"numHistDays:$cda " .
2021-03-20 14:14:18 +00:00
"rainFactorSlope:slider,0,1,100 " .
2020-12-13 17:29:15 +00:00
"showDiff:no,top,bottom " .
"showHeader:1,0 " .
"showLink:1,0 " .
"showNight:1,0 " .
"showWeather:1,0 " .
2021-03-14 11:51:38 +00:00
"spaceSize " .
2020-12-13 17:29:15 +00:00
"Wh/kWh:Wh,kWh " .
2021-01-01 19:44:20 +00:00
"weatherColor:colorpicker,RGB " .
"weatherColor_night:colorpicker,RGB " .
2020-12-13 17:29:15 +00:00
$ readingFnAttributes ;
$ hash - > { FW_hideDisplayName } = 1 ; # Forum 88667
# $hash->{FW_addDetailToSummary} = 1;
# $hash->{FW_atPageEnd} = 1; # wenn 1 -> kein Longpoll ohne informid in HTML-Tag
eval { FHEM::Meta:: InitMod ( __FILE__ , $ hash ) } ; # für Meta.pm (https://forum.fhem.de/index.php/topic,97589.0.html)
return ;
}
###############################################################
# SolarForecast Define
###############################################################
sub Define {
my ( $ hash , $ def ) = @ _ ;
my @ a = split ( /\s+/x , $ def ) ;
2021-01-17 19:13:02 +00:00
return "Error: Perl module " . $ jsonabs . " is missing. Install it on Debian with: sudo apt-get install libjson-perl" if ( $ jsonabs ) ;
2020-12-13 17:29:15 +00:00
2021-01-17 19:13:02 +00:00
my $ name = $ hash - > { NAME } ;
my $ type = $ hash - > { TYPE } ;
2020-12-13 17:29:15 +00:00
$ hash - > { HELPER } { MODMETAABSENT } = 1 if ( $ modMetaAbsent ) ; # Modul Meta.pm nicht vorhanden
2021-01-17 19:13:02 +00:00
my $ params = {
hash = > $ hash ,
notes = > \ % vNotesIntern ,
useAPI = > 0 ,
useSMUtils = > 1 ,
useErrCodes = > 0
} ;
use version 0.77 ; our $ VERSION = moduleVersion ( $ params ) ; # Versionsinformationen setzen
2020-12-13 17:29:15 +00:00
createNotifyDev ( $ hash ) ;
2021-03-13 14:48:27 +00:00
my $ file = $ pvhcache . $ name ; # Cache File PV History lesen wenn vorhanden
my $ cachename = "pvhist" ;
$ params - > { file } = $ file ;
$ params - > { cachename } = $ cachename ;
_readCacheFile ( $ params ) ;
$ file = $ pvrcache . $ name ; # Cache File PV Real lesen wenn vorhanden
$ cachename = "pvreal" ;
$ params - > { file } = $ file ;
$ params - > { cachename } = $ cachename ;
_readCacheFile ( $ params ) ;
readingsSingleUpdate ( $ hash , "state" , "initialized" , 1 ) ;
centralTask ( $ hash ) ; # Einstieg in Abfrage
InternalTimer ( gettimeofday ( ) + $ whistrepeat , "FHEM::SolarForecast::periodicWriteCachefiles" , $ hash , 0 ) ; # Einstieg periodisches Schreiben historische Daten
return ;
}
################################################################
# Cachefile lesen
################################################################
sub _readCacheFile {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ file = $ paref - > { file } ;
my $ cachename = $ paref - > { cachename } ;
my $ name = $ hash - > { NAME } ;
2021-03-14 05:56:30 +00:00
my ( $ error , @ content ) = FileRead ( $ file ) ;
2021-01-17 19:13:02 +00:00
if ( ! $ error ) {
my $ json = join "" , @ content ;
my $ success = evaljson ( $ hash , $ json ) ;
if ( $ success ) {
2021-03-13 14:48:27 +00:00
$ data { $ hash - > { TYPE } } { $ name } { $ cachename } = decode_json ( $ json ) ;
2021-01-17 19:13:02 +00:00
}
else {
2021-03-14 05:56:30 +00:00
Log3 ( $ name , 2 , qq{ $name - WARNING - The content of file "$file" is not readable and may be corrupt } ) ;
2021-01-17 19:13:02 +00:00
}
}
2021-03-13 14:48:27 +00:00
2020-12-13 17:29:15 +00:00
return ;
}
###############################################################
# SolarForecast Set
###############################################################
sub Set {
my ( $ hash , @ a ) = @ _ ;
return "\"set X\" needs at least an argument" if ( @ a < 2 ) ;
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 ;
2021-01-26 20:38:22 +00:00
my ( $ setlist , @ fcdevs , @ cfs ) ;
2020-12-13 17:29:15 +00:00
my ( $ fcd , $ ind , $ med , $ cf ) = ( "" , "" , "" , "" ) ;
return if ( IsDisabled ( $ name ) ) ;
@ fcdevs = devspec2array ( "TYPE=DWD_OpenData" ) ;
$ fcd = join "," , @ fcdevs if ( @ fcdevs ) ;
for my $ h ( @ chours ) {
push @ cfs , "pvCorrectionFactor_" . sprintf ( "%02d" , $ h ) ;
}
$ cf = join " " , @ cfs if ( @ cfs ) ;
$ setlist = "Unknown argument $opt, choose one of " .
2020-12-27 18:51:53 +00:00
"currentForecastDev:$fcd " .
"currentInverterDev:textField-long " .
"currentMeterDev:textField-long " .
2021-01-26 20:38:22 +00:00
"inverterStrings " .
2021-01-27 17:02:18 +00:00
"modulePeakString " .
2021-01-26 20:38:22 +00:00
"moduleTiltAngle " .
"moduleDirection " .
2020-12-15 13:41:10 +00:00
"pvCorrectionFactor_Auto:on,off " .
2021-01-26 20:38:22 +00:00
"reset:currentForecastDev,currentInverterDev,currentMeterDev,inverterStrings,pvCorrection,pvHistory " .
2021-01-23 21:31:11 +00:00
"writeHistory:noArg " .
2020-12-13 17:29:15 +00:00
$ cf
;
my $ params = {
hash = > $ hash ,
name = > $ name ,
opt = > $ opt ,
2020-12-15 13:41:10 +00:00
arg = > $ arg ,
2020-12-13 17:29:15 +00:00
prop = > $ prop ,
prop1 = > $ prop1
} ;
if ( $ hset { $ opt } && defined & { $ hset { $ opt } { fn } } ) {
my $ ret = q{ } ;
$ ret = & { $ hset { $ opt } { fn } } ( $ params ) ;
return $ ret ;
}
return "$setlist" ;
}
################################################################
2020-12-27 18:51:53 +00:00
# Setter currentForecastDev
2020-12-13 17:29:15 +00:00
################################################################
2020-12-27 18:51:53 +00:00
sub _setcurrentForecastDev { ## no critic "not used"
2020-12-13 17:29:15 +00:00
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ prop = $ paref - > { prop } // return qq{ no PV forecast device specified } ;
if ( ! $ defs { $ prop } || $ defs { $ prop } { TYPE } ne "DWD_OpenData" ) {
2021-01-01 14:36:19 +00:00
return qq{ Forecast device "$prop" doesn't exist or has no TYPE "DWD_OpenData" } ; #' :)
2020-12-13 17:29:15 +00:00
}
readingsSingleUpdate ( $ hash , "currentForecastDev" , $ prop , 1 ) ;
createNotifyDev ( $ hash ) ;
return ;
}
################################################################
2020-12-27 18:51:53 +00:00
# Setter currentInverterDev
2020-12-13 17:29:15 +00:00
################################################################
sub _setinverterDevice { ## no critic "not used"
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
2020-12-15 13:41:10 +00:00
my $ opt = $ paref - > { opt } ;
my $ arg = $ paref - > { arg } ;
2020-12-13 17:29:15 +00:00
2020-12-15 13:41:10 +00:00
if ( ! $ arg ) {
return qq{ The command "$opt" needs an argument ! } ;
}
my ( $ a , $ h ) = parseParams ( $ arg ) ;
my $ indev = $ a - > [ 0 ] // "" ;
if ( ! $ indev || ! $ defs { $ indev } ) {
return qq{ The device "$indev" doesn't exist! } ;
2020-12-13 17:29:15 +00:00
}
2021-01-21 18:23:21 +00:00
2021-03-16 14:53:54 +00:00
if ( ! $ h - > { pv } || ! $ h - > { etotal } ) {
2021-01-21 18:23:21 +00:00
return qq{ The syntax of "$opt" isn't right. Please consider the commandref. } ;
}
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
delete $ hash - > { HELPER } { INITETOTAL } ;
2020-12-15 13:41:10 +00:00
readingsSingleUpdate ( $ hash , "currentInverterDev" , $ arg , 1 ) ;
2020-12-13 17:29:15 +00:00
createNotifyDev ( $ hash ) ;
return ;
}
2021-01-26 20:38:22 +00:00
################################################################
# Setter inverterStrings
################################################################
sub _setinverterStrings { ## no critic "not used"
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ prop = $ paref - > { prop } // return qq{ no inverter strings specified } ;
readingsSingleUpdate ( $ hash , "inverterStrings" , $ prop , 1 ) ;
return qq{ REMINDER - After setting or changing "inverterStrings" please check / set all module parameter (e.g. moduleTiltAngle) again ! } ;
}
2020-12-13 17:29:15 +00:00
################################################################
2020-12-27 18:51:53 +00:00
# Setter currentMeterDev
2020-12-13 17:29:15 +00:00
################################################################
sub _setmeterDevice { ## no critic "not used"
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
2020-12-15 13:41:10 +00:00
my $ opt = $ paref - > { opt } ;
my $ arg = $ paref - > { arg } ;
2020-12-13 17:29:15 +00:00
2020-12-15 13:41:10 +00:00
if ( ! $ arg ) {
return qq{ The command "$opt" needs an argument ! } ;
}
my ( $ a , $ h ) = parseParams ( $ arg ) ;
my $ medev = $ a - > [ 0 ] // "" ;
if ( ! $ medev || ! $ defs { $ medev } ) {
return qq{ The device "$medev" doesn't exist! } ;
2020-12-13 17:29:15 +00:00
}
2021-01-21 18:23:21 +00:00
2021-03-17 20:06:19 +00:00
if ( ! $ h - > { gcon } || ! $ h - > { contotal } ) {
2021-01-21 18:23:21 +00:00
return qq{ The syntax of "$opt" isn't right. Please consider the commandref. } ;
}
2020-12-13 17:29:15 +00:00
2020-12-15 13:41:10 +00:00
readingsSingleUpdate ( $ hash , "currentMeterDev" , $ arg , 1 ) ;
2020-12-13 17:29:15 +00:00
createNotifyDev ( $ hash ) ;
return ;
}
################################################################
2021-01-27 17:02:18 +00:00
# Setter modulePeakString
2020-12-13 17:29:15 +00:00
################################################################
2021-01-27 17:02:18 +00:00
sub _setmodulePeakString { ## no critic "not used"
2020-12-13 17:29:15 +00:00
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
2021-01-27 17:02:18 +00:00
my $ arg = $ paref - > { arg } // return qq{ no PV module peak specified } ;
2020-12-13 17:29:15 +00:00
2021-01-26 20:38:22 +00:00
$ arg =~ s/,/./xg ;
2021-01-23 08:01:53 +00:00
2021-01-26 20:38:22 +00:00
my ( $ a , $ h ) = parseParams ( $ arg ) ;
2021-01-24 18:03:44 +00:00
2021-01-26 20:38:22 +00:00
if ( ! keys %$ h ) {
2021-01-27 17:02:18 +00:00
return qq{ The provided PV module peak has wrong format } ;
2021-01-24 18:03:44 +00:00
}
2020-12-27 18:51:53 +00:00
2021-01-26 20:38:22 +00:00
while ( my ( $ key , $ value ) = each %$ h ) {
if ( $ value !~ /[0-9.]/x ) {
2021-01-27 17:02:18 +00:00
return qq{ The module peak of "$key" must be specified by numbers and optionally with decimal places } ;
2021-01-26 20:38:22 +00:00
}
2020-12-27 18:51:53 +00:00
}
2021-01-27 17:02:18 +00:00
readingsSingleUpdate ( $ hash , "modulePeakString" , $ arg , 1 ) ;
2021-01-26 20:38:22 +00:00
my $ ret = createStringConfig ( $ hash ) ;
return $ ret if ( $ ret ) ;
2020-12-13 17:29:15 +00:00
return ;
}
2020-12-16 19:15:48 +00:00
################################################################
# Setter moduleTiltAngle
################################################################
2020-12-27 20:04:17 +00:00
sub _setmoduleTiltAngle { ## no critic "not used"
2020-12-16 19:15:48 +00:00
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
2021-01-26 20:38:22 +00:00
my $ arg = $ paref - > { arg } // return qq{ no tilt angle was provided } ;
my $ tilt = join "|" , sort keys % hff ;
2020-12-16 19:15:48 +00:00
2021-01-26 20:38:22 +00:00
my ( $ a , $ h ) = parseParams ( $ arg ) ;
if ( ! keys %$ h ) {
return qq{ The provided tilt angle has wrong format } ;
2020-12-16 19:15:48 +00:00
}
2021-01-26 20:38:22 +00:00
while ( my ( $ key , $ value ) = each %$ h ) {
if ( $ value !~ /^($tilt)$/x ) {
return qq{ The tilt angle of "$key" is wrong } ;
}
}
readingsSingleUpdate ( $ hash , "moduleTiltAngle" , $ arg , 1 ) ;
my $ ret = createStringConfig ( $ hash ) ;
return $ ret if ( $ ret ) ;
2020-12-16 19:15:48 +00:00
return ;
}
2021-01-24 18:03:44 +00:00
################################################################
# Setter moduleDirection
################################################################
sub _setmoduleDirection { ## no critic "not used"
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
2021-01-26 20:38:22 +00:00
my $ arg = $ paref - > { arg } // return qq{ no module direction was provided } ;
2021-01-24 18:03:44 +00:00
2021-01-26 20:38:22 +00:00
my $ dirs = "N|NE|E|SE|S|SW|W|NW" ; # mögliche Richtungsangaben
my ( $ a , $ h ) = parseParams ( $ arg ) ;
if ( ! keys %$ h ) {
return qq{ The provided module direction has wrong format } ;
}
while ( my ( $ key , $ value ) = each %$ h ) {
if ( $ value !~ /^($dirs)$/x ) {
return qq{ The module direction of "$key" is wrong: $value } ;
}
2021-01-24 18:03:44 +00:00
}
2021-01-26 20:38:22 +00:00
readingsSingleUpdate ( $ hash , "moduleDirection" , $ arg , 1 ) ;
my $ ret = createStringConfig ( $ hash ) ;
return $ ret if ( $ ret ) ;
2021-01-24 18:03:44 +00:00
return ;
}
2020-12-13 17:29:15 +00:00
################################################################
# Setter pvCorrectionFactor
################################################################
sub _setpvCorrectionFactor { ## no critic "not used"
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ opt = $ paref - > { opt } ;
my $ prop = $ paref - > { prop } // return qq{ no correction value specified } ;
if ( $ prop !~ /[0-9,.]/x ) {
return qq{ The correction value must be specified by numbers and optionally with decimal places } ;
}
$ prop =~ s/,/./x ;
2020-12-17 08:44:29 +00:00
readingsSingleUpdate ( $ hash , $ opt , $ prop . " (manual)" , 1 ) ;
2020-12-13 17:29:15 +00:00
my @ da ;
my $ t = time ; # aktuelle Unix-Zeit
my $ chour = strftime "%H" , localtime ( $ t ) ; # aktuelle Stunde
my $ fcdev = ReadingsVal ( $ name , "currentForecastDev" , "" ) ; # aktuelles Forecast Device
my $ params = {
2020-12-17 15:52:48 +00:00
hash = > $ hash ,
name = > $ name ,
2020-12-13 17:29:15 +00:00
t = > $ t ,
chour = > $ chour ,
daref = > \ @ da
} ;
2020-12-27 18:51:53 +00:00
_transferDWDForecastValues ( $ params ) ;
2020-12-13 17:29:15 +00:00
if ( @ da ) {
push @ da , "state:updated" ; # Abschluß state
2020-12-27 18:51:53 +00:00
createReadingsFromArray ( $ hash , \ @ da , 1 ) ;
2020-12-13 17:29:15 +00:00
}
return ;
}
2020-12-15 13:41:10 +00:00
################################################################
# Setter pvCorrectionFactor_Auto
################################################################
sub _setpvCorrectionFactorAuto { ## no critic "not used"
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ opt = $ paref - > { opt } ;
my $ prop = $ paref - > { prop } // return qq{ no correction value specified } ;
readingsSingleUpdate ( $ hash , "pvCorrectionFactor_Auto" , $ prop , 1 ) ;
2021-01-22 09:51:04 +00:00
if ( $ prop eq "off" ) {
deleteReadingspec ( $ hash , "pvCorrectionFactor_.*_autocalc" ) ;
}
2020-12-15 13:41:10 +00:00
return ;
}
2021-01-26 20:38:22 +00:00
################################################################
# Setter reset
################################################################
sub _setreset { ## no critic "not used"
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ prop = $ paref - > { prop } // return qq{ no source specified for reset } ;
if ( $ prop eq "pvHistory" ) {
my $ type = $ hash - > { TYPE } ;
delete $ data { $ type } { $ name } { pvhist } ;
2021-03-16 14:53:54 +00:00
delete $ hash - > { HELPER } { INITETOTAL } ;
2021-01-26 20:38:22 +00:00
return ;
}
if ( $ prop eq "pvCorrection" ) {
deleteReadingspec ( $ hash , "pvCorrectionFactor_.*" ) ;
return ;
}
readingsDelete ( $ hash , $ prop ) ;
if ( $ prop eq "currentMeterDev" ) {
readingsDelete ( $ hash , "Current_GridConsumption" ) ;
}
if ( $ prop eq "currentInverterDev" ) {
readingsDelete ( $ hash , "Current_PV" ) ;
deleteReadingspec ( $ hash , ".*_PVreal" ) ;
2021-03-16 14:53:54 +00:00
delete $ hash - > { HELPER } { INITETOTAL } ;
2021-01-26 20:38:22 +00:00
}
createNotifyDev ( $ hash ) ;
return ;
}
2021-01-23 21:31:11 +00:00
################################################################
# Setter writeHistory
################################################################
sub _setwriteHistory { ## no critic "not used"
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
2021-03-13 14:48:27 +00:00
my $ name = $ paref - > { name } ;
my $ ret = writeCacheToFile ( $ hash , "pvhist" , $ pvhcache . $ name ) ; # Cache File für PV History schreiben
writeCacheToFile ( $ hash , "pvreal" , $ pvrcache . $ name ) ; # Cache File für PV Real schreiben
2021-01-23 21:31:11 +00:00
return $ ret ;
}
2020-12-13 17:29:15 +00:00
###############################################################
# SolarForecast Get
###############################################################
sub Get {
2021-01-17 19:13:02 +00:00
my ( $ hash , @ a ) = @ _ ;
return "\"get X\" needs at least an argument" if ( @ a < 2 ) ;
my $ name = shift @ a ;
my $ opt = shift @ a ;
my $ arg = join " " , map { my $ p = $ _ ; $ p =~ s/\s//xg ; $ p ; } @ a ; ## no critic 'Map blocks'
2020-12-13 17:29:15 +00:00
2021-01-17 19:13:02 +00:00
my $ getlist = "Unknown argument $opt, choose one of " .
"data:noArg " .
"html:noArg " .
2021-03-20 09:05:26 +00:00
"nextHours:noArg " .
2021-03-18 17:25:37 +00:00
"pvForecast:noArg " .
2021-01-26 20:38:22 +00:00
"pvHistory:noArg " .
2021-03-17 20:06:19 +00:00
"pvReal:noArg " .
"stringConfig:noArg " .
"weatherData:noArg "
2021-01-17 19:13:02 +00:00
;
return if ( IsDisabled ( $ name ) ) ;
my $ params = {
hash = > $ hash ,
name = > $ name ,
opt = > $ opt ,
arg = > $ arg
} ;
if ( $ hget { $ opt } && defined & { $ hget { $ opt } { fn } } ) {
my $ ret = q{ } ;
if ( ! $ hash - > { CREDENTIALS } && $ hget { $ opt } { needcred } ) {
return qq{ Credentials of $name are not set." } ;
}
$ ret = & { $ hget { $ opt } { fn } } ( $ params ) ;
return $ ret ;
}
return $ getlist ;
}
###############################################################
# Getter data
###############################################################
sub _getdata {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
return centralTask ( $ hash ) ;
}
###############################################################
# Getter html
###############################################################
sub _gethtml {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
return pageAsHtml ( $ hash ) ;
}
###############################################################
# Getter ftui
# ohne Eintrag in Get-Liste
###############################################################
sub _getftui {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
return pageAsHtml ( $ hash , "ftui" ) ;
}
###############################################################
# Getter listPVHistory
###############################################################
sub _getlistPVHistory {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
2021-03-17 20:06:19 +00:00
my $ name = $ hash - > { NAME } ;
my $ type = $ hash - > { TYPE } ;
my $ ret = listDataPool ( $ hash , "pvhist" ) ;
return $ ret ;
}
###############################################################
# Getter listPVReal
###############################################################
sub _getlistPVReal {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ hash - > { NAME } ;
my $ type = $ hash - > { TYPE } ;
my $ ret = listDataPool ( $ hash , "pvreal" ) ;
return $ ret ;
}
2021-03-18 17:25:37 +00:00
###############################################################
# Getter listPVForecast
###############################################################
sub _getlistPVForecast {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ hash - > { NAME } ;
my $ type = $ hash - > { TYPE } ;
my $ ret = listDataPool ( $ hash , "pvfc" ) ;
return $ ret ;
}
2021-03-20 09:05:26 +00:00
###############################################################
# Getter listNextHours
###############################################################
sub _getlistNextHours {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ hash - > { NAME } ;
my $ type = $ hash - > { TYPE } ;
my $ ret = listDataPool ( $ hash , "nexthours" ) ;
return $ ret ;
}
2021-03-17 20:06:19 +00:00
###############################################################
# Getter listWeather
###############################################################
sub _getlistWeather {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ hash - > { NAME } ;
my $ type = $ hash - > { TYPE } ;
my $ ret = listDataPool ( $ hash , "weather" ) ;
2021-01-17 19:13:02 +00:00
return $ ret ;
2020-12-13 17:29:15 +00:00
}
2021-01-26 20:38:22 +00:00
###############################################################
# Getter stringConfig
###############################################################
sub _getstringConfig {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ ret = checkStringConfig ( $ hash ) ;
return $ ret ;
}
2020-12-13 17:29:15 +00:00
################################################################
sub Attr {
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 ;
}
2021-01-20 21:00:34 +00:00
$ do = 0 if ( $ cmd eq "del" ) ;
2020-12-13 17:29:15 +00:00
$ val = ( $ do == 1 ? "disabled" : "initialized" ) ;
readingsSingleUpdate ( $ hash , "state" , $ val , 1 ) ;
}
if ( $ aName eq "icon" ) {
$ _ [ 2 ] = "consumerAdviceIcon" ;
}
2020-12-15 13:41:10 +00:00
if ( $ cmd eq "set" ) {
if ( $ aName eq "interval" ) {
unless ( $ aVal =~ /^[0-9]+$/x ) { return "The value for $aName is not valid. Use only figures 0-9 !" ; }
InternalTimer ( gettimeofday ( ) + 1.0 , "FHEM::SolarForecast::centralTask" , $ hash , 0 ) ;
2020-12-27 20:04:17 +00:00
}
if ( $ aName eq "maxVariancePerDay" ) {
unless ( $ aVal =~ /^[0-9.]+$/x ) { return "The value for $aName is not valid. Use only numbers with optional decimal places !" ; }
}
2020-12-15 13:41:10 +00:00
}
2020-12-13 17:29:15 +00:00
return ;
}
###################################################################################
# Eventverarbeitung
###################################################################################
sub Notify {
# Es werden nur die Events von Geräten verarbeitet die im Hash $hash->{NOTIFYDEV} gelistet sind (wenn definiert).
# Dadurch kann die Menge der Events verringert werden. In sub DbRep_Define angeben.
my $ myHash = shift ;
my $ dev_hash = shift ;
2021-01-23 08:01:53 +00:00
my $ myName = $ myHash - > { NAME } ; # Name des eigenen Devices
my $ devName = $ dev_hash - > { NAME } ; # Device welches Events erzeugt hat
2020-12-13 17:29:15 +00:00
return if ( IsDisabled ( $ myName ) || ! $ myHash - > { NOTIFYDEV } ) ;
my $ events = deviceEvents ( $ dev_hash , 1 ) ;
return if ( ! $ events ) ;
2020-12-15 13:41:10 +00:00
return ;
}
2020-12-13 17:29:15 +00:00
2021-03-01 21:18:23 +00:00
###############################################################
# DbLog_splitFn
###############################################################
sub DbLogSplit {
my $ event = shift ;
my $ device = shift ;
my ( $ reading , $ value , $ unit ) = ( "" , "" , "" ) ;
if ( $ event =~ /\sk?Wh?$/xs ) {
my @ parts = split ( /\s/x , $ event , 3 ) ;
$ reading = $ parts [ 0 ] ;
$ reading =~ tr /:/ / d ;
$ value = $ parts [ 1 ] ;
$ unit = $ parts [ 2 ] ;
2021-03-14 06:42:12 +00:00
# Log3 ($device, 1, qq{$device - Split for DbLog done -> Reading: $reading, Value: $value, Unit: $unit});
2021-03-01 21:18:23 +00:00
}
return ( $ reading , $ value , $ unit ) ;
}
2021-01-17 19:13:02 +00:00
################################################################
# Shutdown
################################################################
sub Shutdown {
my $ hash = shift ;
my $ name = $ hash - > { NAME } ;
my $ type = $ hash - > { TYPE } ;
2021-03-13 14:48:27 +00:00
writeCacheToFile ( $ hash , "pvhist" , $ pvhcache . $ name ) ; # Cache File für PV History schreiben
writeCacheToFile ( $ hash , "pvreal" , $ pvrcache . $ name ) ; # Cache File für PV Real schreiben
2021-01-17 19:13:02 +00:00
return ;
}
2021-03-13 17:25:42 +00:00
################################################################
# Die Undef-Funktion wird aufgerufen wenn ein Gerät mit delete
# gelöscht wird oder bei der Abarbeitung des Befehls rereadcfg,
# der ebenfalls alle Geräte löscht und danach das
# Konfigurationsfile neu einliest. Entsprechend müssen in der
# Funktion typische Aufräumarbeiten durchgeführt werden wie das
# saubere Schließen von Verbindungen oder das Entfernen von
# internen Timern.
################################################################
sub Undef {
my $ hash = shift ;
my $ arg = shift ;
RemoveInternalTimer ( $ hash ) ;
return ;
}
2021-01-17 19:13:02 +00:00
#################################################################
# Wenn ein Gerät in FHEM gelöscht wird, wird zuerst die Funktion
# X_Undef aufgerufen um offene Verbindungen zu schließen,
# anschließend wird die Funktion X_Delete aufgerufen.
# Funktion: Aufräumen von dauerhaften Daten, welche durch das
# Modul evtl. für dieses Gerät spezifisch erstellt worden sind.
# Es geht hier also eher darum, alle Spuren sowohl im laufenden
# FHEM-Prozess, als auch dauerhafte Daten bspw. im physikalischen
# Gerät zu löschen die mit dieser Gerätedefinition zu tun haben.
#################################################################
sub Delete {
my $ hash = shift ;
my $ arg = shift ;
my $ name = $ hash - > { NAME } ;
2021-03-13 15:50:33 +00:00
my $ file = $ pvhcache . $ name ; # Cache File PV History löschen
my $ error = FileDelete ( $ file ) ;
if ( $ error ) {
Log3 ( $ name , 2 , qq{ $name - ERROR deleting cache file "$file": $error } ) ;
}
$ error = qq{ } ;
$ file = $ pvrcache . $ name ; # Cache File PV Real löschen
$ error = FileDelete ( $ file ) ;
2021-01-17 19:13:02 +00:00
if ( $ error ) {
Log3 ( $ name , 2 , qq{ $name - ERROR deleting cache file "$file": $error } ) ;
}
return ;
}
2020-12-15 13:41:10 +00:00
################################################################
# Zentraler Datenabruf
################################################################
sub centralTask {
my $ hash = shift ;
2021-03-13 15:50:33 +00:00
my $ name = $ hash - > { NAME } ; # Name des eigenen Devices
2021-01-26 20:38:22 +00:00
my $ type = $ hash - > { TYPE } ;
2020-12-13 17:29:15 +00:00
2020-12-15 13:41:10 +00:00
RemoveInternalTimer ( $ hash , "FHEM::SolarForecast::centralTask" ) ;
2021-03-18 16:32:44 +00:00
2021-03-19 13:17:55 +00:00
### nicht mehr benötigte Readings/Daten löschen - kann später wieder raus !!
for my $ i ( keys % { $ data { $ type } { $ name } { pvhist } } ) {
delete $ data { $ type } { $ name } { pvhist } { $ i } { "00" } ;
}
2021-03-18 16:32:44 +00:00
deleteReadingspec ( $ hash , "Today_Hour.*_Consumption" ) ;
2021-03-18 22:12:08 +00:00
deleteReadingspec ( $ hash , "ThisHour_.*" ) ;
2021-03-18 22:30:51 +00:00
deleteReadingspec ( $ hash , "Today_PV" ) ;
deleteReadingspec ( $ hash , "Tomorrow_PV" ) ;
2020-12-15 13:41:10 +00:00
my $ interval = controlParams ( $ name ) ;
2020-12-13 17:29:15 +00:00
2020-12-15 13:41:10 +00:00
if ( $ init_done == 1 ) {
if ( ! $ interval ) {
$ hash - > { MODE } = "Manual" ;
}
else {
my $ new = gettimeofday ( ) + $ interval ;
InternalTimer ( $ new , "FHEM::SolarForecast::centralTask" , $ hash , 0 ) ; # Wiederholungsintervall
$ hash - > { MODE } = "Automatic - next polltime: " . FmtTime ( $ new ) ;
2020-12-13 17:29:15 +00:00
}
2020-12-15 13:41:10 +00:00
return if ( IsDisabled ( $ name ) ) ;
2021-01-26 20:38:22 +00:00
readingsSingleUpdate ( $ hash , "state" , "running" , 1 ) ;
my $ stch = $ data { $ type } { $ name } { strings } ; # String Config Hash
if ( ! keys % { $ stch } ) {
my $ ret = createStringConfig ( $ hash ) ; # die String Konfiguration erstellen
if ( $ ret ) {
readingsSingleUpdate ( $ hash , "state" , $ ret , 1 ) ;
return ;
}
}
2020-12-15 13:41:10 +00:00
my @ da ;
my $ t = time ; # aktuelle Unix-Zeit
my $ chour = strftime "%H" , localtime ( $ t ) ; # aktuelle Stunde
2021-03-16 14:53:54 +00:00
my $ day = strftime "%d" , localtime ( $ t ) ; # aktueller Tag
2020-12-15 13:41:10 +00:00
my $ params = {
2020-12-17 15:52:48 +00:00
hash = > $ hash ,
name = > $ name ,
t = > $ t ,
chour = > $ chour ,
2021-03-16 14:53:54 +00:00
day = > $ day ,
2020-12-17 15:52:48 +00:00
daref = > \ @ da
2020-12-15 13:41:10 +00:00
} ;
2021-03-20 13:55:15 +00:00
Log3 ( $ name , 4 , "$name - ################################################################" ) ;
Log3 ( $ name , 4 , "$name - ### New data collection cycle ###" ) ;
Log3 ( $ name , 4 , "$name - ################################################################" ) ;
Log3 ( $ name , 4 , "$name - current hour of day: " . ( $ chour + 1 ) ) ;
2021-01-23 11:46:54 +00:00
2021-03-19 18:06:34 +00:00
_transferWeatherValues ( $ params ) ; # Wetterwerte übertragen
_transferDWDForecastValues ( $ params ) ; # Forecast Werte übertragen
2020-12-27 18:51:53 +00:00
_transferInverterValues ( $ params ) ; # WR Werte übertragen
_transferMeterValues ( $ params ) ;
2021-03-21 11:50:35 +00:00
2020-12-15 13:41:10 +00:00
if ( @ da ) {
2020-12-27 18:51:53 +00:00
createReadingsFromArray ( $ hash , \ @ da , 1 ) ;
2020-12-13 17:29:15 +00:00
}
2020-12-15 13:41:10 +00:00
2021-01-24 08:35:16 +00:00
collectSummaries ( $ hash , $ chour , \ @ da ) ; # Zusammenfassung nächste 4 Stunden u.a. erstellen
calcVariance ( $ params ) ; # Autokorrektur berechnen
2020-12-15 13:41:10 +00:00
2020-12-27 18:51:53 +00:00
readingsSingleUpdate ( $ hash , "state" , "updated" , 1 ) ; # Abschluß state
2020-12-13 17:29:15 +00:00
}
2020-12-15 13:41:10 +00:00
else {
InternalTimer ( gettimeofday ( ) + 5 , "FHEM::SolarForecast::centralTask" , $ hash , 0 ) ;
2020-12-13 17:29:15 +00:00
}
return ;
}
2021-01-26 20:38:22 +00:00
################################################################
# Erstellen der Stringkonfiguration
# Stringhash: $data{$type}{$name}{strings}
################################################################
sub createStringConfig { ## no critic "not used"
my $ hash = shift ;
my $ name = $ hash - > { NAME } ;
my $ type = $ hash - > { TYPE } ;
delete $ data { $ type } { $ name } { strings } ; # Stringhash zurücksetzen
my @ istrings = split "," , ReadingsVal ( $ name , "inverterStrings" , "" ) ; # Stringbezeichner
if ( ! @ istrings ) {
return qq{ Define all used strings with command "set $name inverterStrings" first. } ;
}
my $ tilt = ReadingsVal ( $ name , "moduleTiltAngle" , "" ) ; # Modul Neigungswinkel für jeden Stringbezeichner
my ( $ at , $ ht ) = parseParams ( $ tilt ) ;
while ( my ( $ key , $ value ) = each %$ ht ) {
if ( $ key ~ ~ @ istrings ) {
$ data { $ type } { $ name } { strings } { "$key" } { tilt } = $ value ;
}
else {
return qq{ Check "moduleTiltAngle" -> the stringname "$key" is not defined as valid string in reading "inverterStrings" } ;
}
}
2021-01-27 17:02:18 +00:00
my $ peak = ReadingsVal ( $ name , "modulePeakString" , "" ) ; # kWp für jeden Stringbezeichner
my ( $ aa , $ ha ) = parseParams ( $ peak ) ;
2021-01-26 20:38:22 +00:00
while ( my ( $ key , $ value ) = each %$ ha ) {
if ( $ key ~ ~ @ istrings ) {
2021-01-27 17:02:18 +00:00
$ data { $ type } { $ name } { strings } { "$key" } { peak } = $ value ;
2021-01-26 20:38:22 +00:00
}
else {
2021-01-27 17:02:18 +00:00
return qq{ Check "modulePeakString" -> the stringname "$key" is not defined as valid string in reading "inverterStrings" } ;
2021-01-26 20:38:22 +00:00
}
}
my $ dir = ReadingsVal ( $ name , "moduleDirection" , "" ) ; # Modul Ausrichtung für jeden Stringbezeichner
my ( $ ad , $ hd ) = parseParams ( $ dir ) ;
while ( my ( $ key , $ value ) = each %$ hd ) {
if ( $ key ~ ~ @ istrings ) {
$ data { $ type } { $ name } { strings } { "$key" } { dir } = $ value ;
}
else {
return qq{ Check "moduleDirection" -> the stringname "$key" is not defined as valid string in reading "inverterStrings" } ;
}
}
if ( ! keys % { $ data { $ type } { $ name } { strings } } ) {
2021-01-27 17:02:18 +00:00
return qq{ The string configuration is empty. \ nPlease check the settings of inverterStrings, modulePeakString, moduleDirection, moduleTiltAngle } ;
2021-01-26 20:38:22 +00:00
}
my @ sca = keys % { $ data { $ type } { $ name } { strings } } ; # Gegencheck ob nicht mehr Strings in inverterStrings enthalten sind als eigentlich verwendet
my @ tom ;
for my $ sn ( @ istrings ) {
next if ( $ sn ~ ~ @ sca ) ;
push @ tom , $ sn ;
}
if ( @ tom ) {
return qq{ Some Strings are not used. Please delete this string names from "inverterStrings" : } . join "," , @ tom ;
}
return ;
}
2020-12-15 13:41:10 +00:00
################################################################
# Steuerparameter berechnen / festlegen
################################################################
sub controlParams {
my $ name = shift ;
my $ interval = AttrVal ( $ name , "interval" , $ definterval ) ; # 0 wenn manuell gesteuert
return $ interval ;
}
2021-01-23 21:31:11 +00:00
################################################################
# Timer für historische Daten schreiben
################################################################
2021-03-13 14:48:27 +00:00
sub periodicWriteCachefiles {
2021-01-23 21:31:11 +00:00
my $ hash = shift ;
my $ name = $ hash - > { NAME } ;
2021-03-13 14:48:27 +00:00
RemoveInternalTimer ( $ hash , "FHEM::SolarForecast::periodicWriteCachefiles" ) ;
InternalTimer ( gettimeofday ( ) + $ whistrepeat , "FHEM::SolarForecast::periodicWriteCachefiles" , $ hash , 0 ) ;
2021-01-23 21:31:11 +00:00
return if ( IsDisabled ( $ name ) ) ;
2021-03-13 14:48:27 +00:00
writeCacheToFile ( $ hash , "pvhist" , $ pvhcache . $ name ) ; # Cache File für PV History schreiben
writeCacheToFile ( $ hash , "pvreal" , $ pvrcache . $ name ) ; # Cache File für PV Real schreiben
2021-01-23 21:31:11 +00:00
return ;
}
################################################################
# historische Daten in File wegschreiben
################################################################
2021-03-13 14:48:27 +00:00
sub writeCacheToFile {
my $ hash = shift ;
my $ cachename = shift ;
my $ file = shift ;
2021-01-23 21:31:11 +00:00
my $ name = $ hash - > { NAME } ;
my $ type = $ hash - > { TYPE } ;
2021-03-13 18:59:54 +00:00
return if ( ! $ data { $ type } { $ name } { $ cachename } ) ;
2021-03-13 14:48:27 +00:00
my @ pvh ;
2021-03-13 20:15:09 +00:00
my $ json = encode_json ( $ data { $ type } { $ name } { $ cachename } ) ;
2021-03-13 14:48:27 +00:00
push @ pvh , $ json ;
2021-01-23 21:31:11 +00:00
2021-03-13 14:48:27 +00:00
my $ error = FileWrite ( $ file , @ pvh ) ;
if ( $ error ) {
my $ err = qq{ ERROR writing cache file "$file": $error } ;
Log3 ( $ name , 2 , "$name - $err" ) ;
return $ err ;
}
else {
my $ lw = gettimeofday ( ) ;
$ hash - > { HISTFILE } = "last write time: " . FmtTime ( $ lw ) . " File: $file" if ( $ cachename eq "pvhist" ) ;
}
2021-01-23 21:31:11 +00:00
return ;
}
2020-12-13 17:29:15 +00:00
################################################################
2020-12-27 18:51:53 +00:00
# Forecast Werte Device (DWD_OpenData) ermitteln und
# übertragen
2020-12-13 17:29:15 +00:00
################################################################
2020-12-27 18:51:53 +00:00
sub _transferDWDForecastValues {
2020-12-17 15:52:48 +00:00
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
2021-03-18 22:43:41 +00:00
my $ t = $ paref - > { t } ; # Epoche Zeit
2020-12-17 15:52:48 +00:00
my $ chour = $ paref - > { chour } ;
my $ daref = $ paref - > { daref } ;
2020-12-13 17:29:15 +00:00
2021-03-18 22:43:41 +00:00
my $ fcname = ReadingsVal ( $ name , "currentForecastDev" , "" ) ; # aktuelles Forecast Device
2020-12-15 13:41:10 +00:00
return if ( ! $ fcname || ! $ defs { $ fcname } ) ;
2020-12-13 17:29:15 +00:00
2020-12-27 18:51:53 +00:00
my ( $ time_str , $ epoche ) ;
2021-03-20 11:36:59 +00:00
my $ type = $ hash - > { TYPE } ;
2020-12-13 17:29:15 +00:00
2021-01-23 11:46:54 +00:00
my @ aneeded = checkdwdattr ( $ fcname ) ;
if ( @ aneeded ) {
2021-01-23 19:30:58 +00:00
Log3 ( $ name , 2 , qq{ $name - ERROR - the attribute "forecastProperties" of device "$fcname" must contain: } . join "," , @ aneeded ) ;
2021-01-23 11:46:54 +00:00
}
2021-03-16 14:53:54 +00:00
for my $ num ( 0 .. 47 ) {
my ( $ fd , $ fh ) = _calcDayHourMove ( $ chour , $ num ) ;
2021-03-18 16:32:44 +00:00
if ( $ fd > 1 ) { # überhängende Readings löschen
2021-03-21 11:50:35 +00:00
deleteReadingspec ( $ hash , "NextHour" . ( $ num - 1 ) . ".*" ) ;
delete $ data { $ type } { $ name } { nexthours } { "NextHour" . sprintf ( "%02d" , $ num - 1 ) } ;
2021-03-18 16:32:44 +00:00
next ;
}
2020-12-13 17:29:15 +00:00
2020-12-27 18:51:53 +00:00
my $ v = ReadingsVal ( $ fcname , "fc${fd}_${fh}_Rad1h" , 0 ) ;
2020-12-13 17:29:15 +00:00
2021-03-21 11:50:35 +00:00
Log3 ( $ name , 5 , "$name - collect DWD forecast data: device=$fcname, rad=fc${fd}_${fh}_Rad1h, Rad1h=$v" ) ;
2020-12-16 19:15:48 +00:00
2021-03-20 09:05:26 +00:00
my $ calcpv = calcPVforecast ( $ name , $ v , $ num , $ t , $ fd ) ; # Vorhersage gewichtet kalkulieren
2021-03-20 21:26:12 +00:00
2021-03-21 11:50:35 +00:00
my $ num1 = $ num - 1 ;
if ( $ num1 >= 0 ) {
$ time_str = "NextHour" . sprintf "%02d" , $ num1 ;
2021-03-21 12:25:45 +00:00
$ epoche = $ t + ( 3600 * $ num1 ) ; # ACHTUNG ! hier $num statt $num1 verwenden !
2021-03-21 11:50:35 +00:00
my $ ta = TimeAdjust ( $ epoche ) ;
push @$ daref , "${time_str}_PVforecast:" . $ calcpv . " Wh" ;
push @$ daref , "${time_str}_Time:" . $ ta ;
$ data { $ type } { $ name } { nexthours } { $ time_str } { pvforecast } = $ calcpv ;
$ data { $ type } { $ name } { nexthours } { $ time_str } { starttime } = $ ta ;
}
2020-12-13 17:29:15 +00:00
2021-03-21 11:50:35 +00:00
if ( $ num < 23 && $ fh < 24 ) { # Ringspeicher PV forecast Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350
$ data { $ type } { $ name } { pvfc } { sprintf ( "%02d" , $ fh + 1 ) } = $ calcpv ;
2021-03-19 13:37:06 +00:00
}
2020-12-13 17:29:15 +00:00
2021-01-26 20:38:22 +00:00
$ hash - > { HELPER } { "fc${fd}_" . sprintf ( "%02d" , $ fh ) . "_Rad1h" } = $ v . " kJ/m2" ; # nur Info: original Vorhersage Strahlungsdaten zur Berechnung Auto-Korrekturfaktor in Helper speichern
2020-12-13 17:29:15 +00:00
2020-12-16 19:15:48 +00:00
if ( $ fd == 0 && int $ calcpv > 0 ) { # Vorhersagedaten des aktuellen Tages zum manuellen Vergleich in Reading speichern
2021-03-19 13:17:55 +00:00
push @$ daref , "Today_Hour" . sprintf ( "%02d" , $ fh ) . "_PVforecast:$calcpv Wh" ;
2021-01-17 19:13:02 +00:00
}
2021-03-19 13:17:55 +00:00
if ( $ fd == 0 && $ fh ) {
2021-03-13 20:15:09 +00:00
$ paref - > { calcpv } = $ calcpv ;
$ paref - > { histname } = "pvfc" ;
2021-03-14 06:29:17 +00:00
$ paref - > { nhour } = sprintf ( "%02d" , $ fh ) ;
2021-01-17 19:13:02 +00:00
setPVhistory ( $ paref ) ;
2021-03-13 20:15:09 +00:00
delete $ paref - > { histname } ;
2020-12-16 15:48:38 +00:00
}
2020-12-27 18:51:53 +00:00
}
return ;
}
################################################################
# Wetter Werte aus dem angebenen Wetterdevice extrahieren
################################################################
2021-01-23 11:46:54 +00:00
sub _transferWeatherValues {
2020-12-27 18:51:53 +00:00
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
2021-03-18 22:43:41 +00:00
my $ t = $ paref - > { t } ; # Epoche Zeit
2020-12-27 18:51:53 +00:00
my $ chour = $ paref - > { chour } ;
my $ daref = $ paref - > { daref } ;
my $ fcname = ReadingsVal ( $ name , "currentForecastDev" , "" ) ; # aktuelles Forecast Device
return if ( ! $ fcname || ! $ defs { $ fcname } ) ;
2021-03-18 17:25:37 +00:00
my $ type = $ hash - > { TYPE } ;
2021-03-21 11:50:35 +00:00
my ( $ time_str ) ;
2020-12-27 18:51:53 +00:00
my $ fc0_SunRise = ReadingsVal ( $ fcname , "fc0_SunRise" , "00:00" ) ; # Sonnenaufgang heute
my $ fc0_SunSet = ReadingsVal ( $ fcname , "fc0_SunSet" , "00:00" ) ; # Sonnenuntergang heute
my $ fc1_SunRise = ReadingsVal ( $ fcname , "fc1_SunRise" , "00:00" ) ; # Sonnenaufgang morgen
my $ fc1_SunSet = ReadingsVal ( $ fcname , "fc1_SunSet" , "00:00" ) ; # Sonnenuntergang morgen
push @$ daref , "Today_SunRise:" . $ fc0_SunRise ;
push @$ daref , "Today_SunSet:" . $ fc0_SunSet ;
push @$ daref , "Tomorrow_SunRise:" . $ fc1_SunRise ;
push @$ daref , "Tomorrow_SunSet:" . $ fc1_SunSet ;
2021-03-16 14:53:54 +00:00
my $ fc0_SunRise_round = sprintf "%02d" , ( split ":" , $ fc0_SunRise ) [ 0 ] ;
my $ fc0_SunSet_round = sprintf "%02d" , ( split ":" , $ fc0_SunSet ) [ 0 ] ;
my $ fc1_SunRise_round = sprintf "%02d" , ( split ":" , $ fc1_SunRise ) [ 0 ] ;
my $ fc1_SunSet_round = sprintf "%02d" , ( split ":" , $ fc1_SunSet ) [ 0 ] ;
2020-12-31 09:09:49 +00:00
2020-12-27 18:51:53 +00:00
for my $ num ( 0 .. 47 ) {
2021-03-16 14:53:54 +00:00
my ( $ fd , $ fh ) = _calcDayHourMove ( $ chour , $ num ) ;
last if ( $ fd > 1 ) ;
2020-12-13 17:29:15 +00:00
2021-03-14 18:20:19 +00:00
my $ wid = ReadingsNum ( $ fcname , "fc${fd}_${fh}_ww" , - 1 ) ;
2021-01-21 22:07:18 +00:00
my $ neff = ReadingsNum ( $ fcname , "fc${fd}_${fh}_Neff" , 0 ) ; # Effektive Wolkendecke
2021-01-22 20:16:33 +00:00
my $ r101 = ReadingsNum ( $ fcname , "fc${fd}_${fh}_R101" , 0 ) ; # Niederschlagswahrscheinlichkeit> 0,1 mm während der letzten Stunde
2020-12-27 18:51:53 +00:00
2021-03-20 11:36:59 +00:00
my $ fhstr = sprintf "%02d" , $ fh ; # hier kann Tag/Nacht-Grenze verstellt werden
2020-12-13 17:29:15 +00:00
2020-12-31 09:09:49 +00:00
if ( $ fd == 0 && ( $ fhstr lt $ fc0_SunRise_round || $ fhstr gt $ fc0_SunSet_round ) ) { # Zeit vor Sonnenaufgang oder nach Sonnenuntergang heute
2021-01-01 19:44:20 +00:00
$ wid += 100 ; # "1" der WeatherID voranstellen wenn Nacht
2020-12-13 17:29:15 +00:00
}
2021-03-16 14:53:54 +00:00
elsif ( $ fd == 1 && ( $ fhstr lt $ fc1_SunRise_round || $ fhstr gt $ fc1_SunSet_round ) ) { # Zeit vor Sonnenaufgang oder nach Sonnenuntergang morgen
2021-01-01 19:44:20 +00:00
$ wid += 100 ; # "1" der WeatherID voranstellen wenn Nacht
2020-12-13 17:29:15 +00:00
}
2021-01-01 14:36:19 +00:00
my $ txt = ReadingsVal ( $ fcname , "fc${fd}_${fh}_wwd" , '' ) ;
2021-03-16 14:53:54 +00:00
2021-01-23 11:46:54 +00:00
Log3 ( $ name , 5 , "$name - collect Weather data: device=$fcname, wid=fc${fd}_${fh}_ww, val=$wid, txt=$txt, cc=$neff, rp=$r101" ) ;
2021-01-01 14:36:19 +00:00
2021-03-21 11:50:35 +00:00
my $ num1 = $ num - 1 ;
if ( $ num1 >= 0 ) {
$ time_str = "NextHour" . sprintf "%02d" , $ num1 ;
$ hash - > { HELPER } { "${time_str}_WeatherId" } = $ wid ;
$ hash - > { HELPER } { "${time_str}_CloudCover" } = $ neff ;
$ hash - > { HELPER } { "${time_str}_RainProb" } = $ r101 ;
$ data { $ type } { $ name } { nexthours } { $ time_str } { weatherid } = $ wid ;
$ data { $ type } { $ name } { nexthours } { $ time_str } { cloudcover } = $ neff ;
$ data { $ type } { $ name } { nexthours } { $ time_str } { rainprob } = $ r101 ;
}
2021-03-20 21:26:12 +00:00
2021-03-20 09:05:26 +00:00
if ( $ num < 23 && $ fh < 23 ) { # Ringspeicher Weather Forum: https://forum.fhem.de/index.php/topic,117864.msg1139251.html#msg1139251
2021-03-19 18:22:23 +00:00
$ data { $ type } { $ name } { weather } { sprintf ( "%02d" , $ fh + 1 ) } { id } = $ wid ;
$ data { $ type } { $ name } { weather } { sprintf ( "%02d" , $ fh + 1 ) } { txt } = $ txt ;
$ data { $ type } { $ name } { weather } { sprintf ( "%02d" , $ fh + 1 ) } { cloudcover } = $ neff ;
$ data { $ type } { $ name } { weather } { sprintf ( "%02d" , $ fh + 1 ) } { rainprob } = $ r101 ;
2021-03-18 17:25:37 +00:00
}
2020-12-13 17:29:15 +00:00
}
return ;
}
################################################################
# Werte Inverter Device ermitteln und übertragen
################################################################
sub _transferInverterValues {
2020-12-17 15:52:48 +00:00
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
2021-01-17 19:13:02 +00:00
my $ t = $ paref - > { t } ; # aktuelle Unix-Zeit
2020-12-17 15:52:48 +00:00
my $ chour = $ paref - > { chour } ;
2021-03-16 14:53:54 +00:00
my $ day = $ paref - > { day } ;
2020-12-17 15:52:48 +00:00
my $ daref = $ paref - > { daref } ;
2020-12-13 17:29:15 +00:00
2020-12-17 15:52:48 +00:00
my $ indev = ReadingsVal ( $ name , "currentInverterDev" , "" ) ;
my ( $ a , $ h ) = parseParams ( $ indev ) ;
$ indev = $ a - > [ 0 ] // "" ;
2020-12-15 13:41:10 +00:00
return if ( ! $ indev || ! $ defs { $ indev } ) ;
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
my $ tlim = "23" ; # Stunde 23 -> bestimmte Aktionen
my $ type = $ hash - > { TYPE } ;
2020-12-19 07:34:35 +00:00
2020-12-16 08:11:12 +00:00
if ( $ chour =~ /^($tlim)$/x ) {
2020-12-17 15:52:48 +00:00
deleteReadingspec ( $ hash , "Today_Hour.*_PV.*" ) ;
2021-03-16 14:53:54 +00:00
delete $ hash - > { HELPER } { INITETOTAL } ;
2020-12-13 17:29:15 +00:00
}
2020-12-16 08:11:12 +00:00
my ( $ pvread , $ pvunit ) = split ":" , $ h - > { pv } ; # Readingname/Unit für aktuelle PV Erzeugung
2021-03-16 14:53:54 +00:00
my ( $ edread , $ etunit ) = split ":" , $ h - > { etotal } ; # Readingname/Unit für Energie total
2020-12-15 13:41:10 +00:00
2021-03-17 20:06:19 +00:00
return if ( ! $ pvread || ! $ edread ) ;
2021-03-16 14:53:54 +00:00
Log3 ( $ name , 5 , "$name - collect Inverter data: device=$indev, pv=$pvread ($pvunit), etotal=$edread ($etunit)" ) ;
2020-12-15 13:41:10 +00:00
my $ pvuf = $ pvunit =~ /^kW$/xi ? 1000 : 1 ;
2020-12-16 08:11:12 +00:00
my $ pv = ReadingsNum ( $ indev , $ pvread , 0 ) * $ pvuf ; # aktuelle Erzeugung (W)
2020-12-13 17:29:15 +00:00
push @$ daref , "Current_PV:" . $ pv . " W" ;
2021-03-16 14:53:54 +00:00
$ data { $ type } { $ name } { current } { generation } = $ pv ; # Hilfshash Wert current generation Forum: https://forum.fhem.de/index.php/topic,117864.msg1139251.html#msg1139251
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
my $ etuf = $ etunit =~ /^kWh$/xi ? 1000 : 1 ;
my $ etotal = ReadingsNum ( $ indev , $ edread , 0 ) * $ etuf ; # Erzeugung total (Wh)
2020-12-15 13:41:10 +00:00
2020-12-17 08:44:29 +00:00
my $ edaypast = 0 ;
2021-03-16 14:53:54 +00:00
2021-03-14 15:06:33 +00:00
for my $ hour ( 0 .. int $ chour ) { # alle bisherigen Erzeugungen des Tages summieren
2021-03-13 15:50:33 +00:00
$ edaypast += ReadingsNum ( $ name , "Today_Hour" . sprintf ( "%02d" , $ hour ) . "_PVreal" , 0 ) ;
2020-12-13 17:29:15 +00:00
}
2021-03-16 14:53:54 +00:00
my $ do = 0 ;
2021-03-17 20:06:19 +00:00
if ( $ edaypast == 0 ) { # Management der Stundenberechnung auf Basis Totalwerte
2021-03-16 14:53:54 +00:00
if ( defined $ hash - > { HELPER } { INITETOTAL } ) {
$ do = 1 ;
}
else {
$ hash - > { HELPER } { INITETOTAL } = $ etotal ;
}
}
elsif ( ! defined $ hash - > { HELPER } { INITETOTAL } ) {
$ hash - > { HELPER } { INITETOTAL } = $ etotal - $ edaypast - ReadingsNum ( $ name , "Today_Hour" . sprintf ( "%02d" , $ chour + 1 ) . "_PVreal" , 0 ) ;
}
else {
$ do = 1 ;
}
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
if ( $ do ) {
my $ ethishour = int ( $ etotal - ( $ edaypast + $ hash - > { HELPER } { INITETOTAL } ) ) ;
# Log3($name, 1, "$name - etotal: $etotal, edaypast: $edaypast, HELPER: $hash->{HELPER}{INITETOTAL}, ethishour: $ethishour ");
2020-12-27 18:51:53 +00:00
if ( $ ethishour < 0 ) {
2021-01-17 19:13:02 +00:00
$ ethishour = 0 ;
2020-12-27 18:51:53 +00:00
}
2021-03-16 14:53:54 +00:00
2021-03-13 17:25:42 +00:00
my $ nhour = $ chour + 1 ;
push @$ daref , "Today_Hour" . sprintf ( "%02d" , $ nhour ) . "_PVreal:" . $ ethishour . " Wh" ;
2021-03-21 11:50:35 +00:00
$ data { $ type } { $ name } { pvreal } { sprintf ( "%02d" , $ nhour ) } = $ ethishour ; # Ringspeicher PV real Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350
2021-01-17 19:13:02 +00:00
$ paref - > { ethishour } = $ ethishour ;
2021-03-14 05:56:30 +00:00
$ paref - > { nhour } = sprintf ( "%02d" , $ nhour ) ;
2021-03-13 20:15:09 +00:00
$ paref - > { histname } = "pvrl" ;
2021-01-17 19:13:02 +00:00
setPVhistory ( $ paref ) ;
2021-03-13 20:15:09 +00:00
delete $ paref - > { histname } ;
2021-03-16 14:53:54 +00:00
}
2021-01-17 19:13:02 +00:00
2020-12-13 17:29:15 +00:00
return ;
}
################################################################
# Werte Meter Device ermitteln und übertragen
################################################################
sub _transferMeterValues {
2020-12-17 15:52:48 +00:00
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ t = $ paref - > { t } ;
my $ chour = $ paref - > { chour } ;
my $ daref = $ paref - > { daref } ;
2020-12-13 17:29:15 +00:00
2021-03-17 20:06:19 +00:00
my $ medev = ReadingsVal ( $ name , "currentMeterDev" , "" ) ; # aktuelles Meter device
2020-12-17 15:52:48 +00:00
my ( $ a , $ h ) = parseParams ( $ medev ) ;
$ medev = $ a - > [ 0 ] // "" ;
2020-12-15 13:41:10 +00:00
return if ( ! $ medev || ! $ defs { $ medev } ) ;
2020-12-13 17:29:15 +00:00
2021-03-17 20:06:19 +00:00
my $ tlim = "23" ; # Stunde 23 -> bestimmte Aktionen
my $ type = $ hash - > { TYPE } ;
2020-12-15 13:41:10 +00:00
2021-03-17 20:06:19 +00:00
if ( $ chour =~ /^($tlim)$/x ) {
2021-03-18 10:06:40 +00:00
deleteReadingspec ( $ hash , "Today_Hour.*_GridConsumption" ) ;
2021-03-17 20:06:19 +00:00
delete $ hash - > { HELPER } { INITCONTOTAL } ;
}
2020-12-13 17:29:15 +00:00
2021-03-17 20:06:19 +00:00
my ( $ gc , $ gcunit ) = split ":" , $ h - > { gcon } ; # Readingname/Unit für aktuellen Netzbezug
my ( $ gt , $ ctunit ) = split ":" , $ h - > { contotal } ; # Readingname/Unit für Bezug total
return if ( ! $ gc || ! $ gt ) ;
Log3 ( $ name , 5 , "$name - collect Meter data: device=$medev, gcon=$gc ($gcunit), contotal=$gt ($ctunit)" ) ;
2020-12-15 13:41:10 +00:00
2021-03-17 20:06:19 +00:00
my $ gcuf = $ gcunit =~ /^kW$/xi ? 1000 : 1 ;
my $ co = ReadingsNum ( $ medev , $ gc , 0 ) * $ gcuf ; # aktueller Bezug (W)
2020-12-15 13:41:10 +00:00
push @$ daref , "Current_GridConsumption:" . $ co . " W" ;
2021-03-17 20:06:19 +00:00
$ data { $ type } { $ name } { current } { consumption } = $ co ; # Hilfshash Wert current grid consumption Forum: https://forum.fhem.de/index.php/topic,117864.msg1139251.html#msg1139251
my $ ctuf = $ ctunit =~ /^kWh$/xi ? 1000 : 1 ;
my $ gctotal = ReadingsNum ( $ medev , $ gt , 0 ) * $ ctuf ; # Bezug total (Wh)
my $ cdaypast = 0 ;
for my $ hour ( 0 .. int $ chour ) { # alle bisherigen Erzeugungen des Tages summieren
2021-03-18 10:06:40 +00:00
$ cdaypast += ReadingsNum ( $ name , "Today_Hour" . sprintf ( "%02d" , $ hour ) . "_GridConsumption" , 0 ) ;
2021-03-17 20:06:19 +00:00
}
my $ do = 0 ;
if ( $ cdaypast == 0 ) { # Management der Stundenberechnung auf Basis Totalwerte
if ( defined $ hash - > { HELPER } { INITCONTOTAL } ) {
$ do = 1 ;
}
else {
$ hash - > { HELPER } { INITCONTOTAL } = $ gctotal ;
}
}
elsif ( ! defined $ hash - > { HELPER } { INITCONTOTAL } ) {
2021-03-18 10:06:40 +00:00
$ hash - > { HELPER } { INITCONTOTAL } = $ gctotal - $ cdaypast - ReadingsNum ( $ name , "Today_Hour" . sprintf ( "%02d" , $ chour + 1 ) . "_GridConsumption" , 0 ) ;
2021-03-17 20:06:19 +00:00
}
else {
$ do = 1 ;
}
if ( $ do ) {
my $ gctotthishour = int ( $ gctotal - ( $ cdaypast + $ hash - > { HELPER } { INITCONTOTAL } ) ) ;
# Log3($name, 1, "$name - gctotal: $gctotal, cdaypast: $cdaypast, HELPER: $hash->{HELPER}{INITCONTOTAL}, gctotthishour: $gctotthishour ");
if ( $ gctotthishour < 0 ) {
$ gctotthishour = 0 ;
}
my $ nhour = $ chour + 1 ;
2021-03-18 10:06:40 +00:00
push @$ daref , "Today_Hour" . sprintf ( "%02d" , $ nhour ) . "_GridConsumption:" . $ gctotthishour . " Wh" ;
2021-03-17 20:06:19 +00:00
$ data { $ type } { $ name } { consumption } { sprintf ( "%02d" , $ nhour ) } = $ gctotthishour ; # Hilfshash Wert Bezug (Wh) Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350
$ paref - > { gctotthishour } = $ gctotthishour ;
$ paref - > { nhour } = sprintf ( "%02d" , $ nhour ) ;
$ paref - > { histname } = "cons" ;
setPVhistory ( $ paref ) ;
delete $ paref - > { histname } ;
}
2020-12-13 17:29:15 +00:00
return ;
}
2021-03-16 14:53:54 +00:00
################################################################
# Berechnen Forecast Tag / Stunden Verschieber
# aus aktueller Stunde + lfd. Nummer
################################################################
sub _calcDayHourMove {
my $ chour = shift ;
my $ num = shift ;
my $ fh = $ chour + $ num ;
my $ fd = int ( $ fh / 24 ) ;
$ fh = $ fh - ( $ fd * 24 ) ;
return ( $ fd , $ fh ) ;
}
2020-12-13 17:29:15 +00:00
################################################################
# FHEMWEB Fn
################################################################
sub FwFn {
my ( $ FW_wname , $ d , $ room , $ pageHash ) = @ _ ; # pageHash is set for summaryFn.
2020-12-17 15:52:48 +00:00
my $ hash = $ defs { $ d } ;
2020-12-13 17:29:15 +00:00
my $ height ;
2020-12-15 13:41:10 +00:00
RemoveInternalTimer ( $ hash , \ & pageRefresh ) ;
2020-12-13 17:29:15 +00:00
$ hash - > { HELPER } { FW } = $ FW_wname ;
my $ link = forecastGraphic ( $ d ) ;
my $ alias = AttrVal ( $ d , "alias" , $ d ) ; # Linktext als Aliasname oder Devicename setzen
my $ dlink = "<a href=\"/fhem?detail=$d\">$alias</a>" ;
my $ ret = "" ;
if ( IsDisabled ( $ d ) ) {
$ height = AttrNum ( $ d , 'beamHeight' , 200 ) ;
$ ret . = "<table class='roomoverview'>" ;
$ ret . = "<tr style='height:" . $ height . "px'>" ;
$ ret . = "<td>" ;
$ ret . = "Solar forecast graphic device <a href=\"/fhem?detail=$d\">$d</a> is disabled" ;
$ ret . = "</td>" ;
$ ret . = "</tr>" ;
$ ret . = "</table>" ;
}
else {
$ ret . = "<span>$dlink </span><br>" if ( AttrVal ( $ d , "showLink" , 0 ) ) ;
$ ret . = $ link ;
}
# Autorefresh nur des aufrufenden FHEMWEB-Devices
my $ al = AttrVal ( $ d , "autoRefresh" , 0 ) ;
if ( $ al ) {
InternalTimer ( gettimeofday ( ) + $ al , \ & pageRefresh , $ hash , 0 ) ;
Log3 ( $ d , 5 , "$d - next start of autoRefresh: " . FmtDateTime ( gettimeofday ( ) + $ al ) ) ;
}
return $ ret ;
}
################################################################
sub pageRefresh {
2020-12-17 15:52:48 +00:00
my $ hash = shift ;
my $ d = $ hash - > { NAME } ;
2020-12-13 17:29:15 +00:00
# Seitenrefresh festgelegt durch SolarForecast-Attribut "autoRefresh" und "autoRefreshFW"
my $ rd = AttrVal ( $ d , "autoRefreshFW" , $ hash - > { HELPER } { FW } ) ;
{ map { FW_directNotify ( "#FHEMWEB:$_" , "location.reload('true')" , "" ) } $ rd }
my $ al = AttrVal ( $ d , "autoRefresh" , 0 ) ;
2020-12-15 13:41:10 +00:00
2020-12-13 17:29:15 +00:00
if ( $ al ) {
InternalTimer ( gettimeofday ( ) + $ al , \ & pageRefresh , $ hash , 0 ) ;
Log3 ( $ d , 5 , "$d - next start of autoRefresh: " . FmtDateTime ( gettimeofday ( ) + $ al ) ) ;
}
else {
2020-12-15 13:41:10 +00:00
RemoveInternalTimer ( $ hash , \ & pageRefresh ) ;
2020-12-13 17:29:15 +00:00
}
return ;
}
################################################################
# Grafik als HTML zurück liefern (z.B. für Widget)
################################################################
sub pageAsHtml {
2020-12-17 15:52:48 +00:00
my $ hash = shift ;
my $ ftui = shift ;
my $ name = $ hash - > { NAME } ;
2020-12-13 17:29:15 +00:00
my $ height ;
my $ link = forecastGraphic ( $ name , $ ftui ) ;
my $ alias = AttrVal ( $ name , "alias" , $ name ) ; # Linktext als Aliasname oder Devicename setzen
my $ dlink = "<a href=\"/fhem?detail=$name\">$alias</a>" ;
my $ ret = "<html>" ;
if ( IsDisabled ( $ name ) ) {
$ height = AttrNum ( $ name , 'beamHeight' , 200 ) ;
$ ret . = "<table class='roomoverview'>" ;
$ ret . = "<tr style='height:" . $ height . "px'>" ;
$ ret . = "<td>" ;
$ ret . = "SMA Portal graphic device <a href=\"/fhem?detail=$name\">$name</a> is disabled" ;
$ ret . = "</td>" ;
$ ret . = "</tr>" ;
$ ret . = "</table>" ;
}
else {
$ ret . = "<span>$dlink </span><br>" if ( AttrVal ( $ name , "showLink" , 0 ) ) ;
$ ret . = $ link ;
}
$ ret . = "</html>" ;
return $ ret ;
}
###############################################################################
# Subroutine für Vorhersagegrafik
###############################################################################
sub forecastGraphic { ## no critic 'complexity'
my $ name = shift ;
my $ ftui = shift // "" ;
my $ hash = $ defs { $ name } ;
my $ ret = "" ;
my ( $ val , $ height ) ;
my ( $ z2 , $ z3 , $ z4 ) ;
my $ he ; # Balkenhöhe
2021-03-14 18:20:19 +00:00
my ( % beam1 , % is , % t , % we , % di , % beam2 ) ;
2020-12-13 17:29:15 +00:00
##########################################################
# Kontext des SolarForecast-Devices speichern für Refresh
$ hash - > { HELPER } { SPGDEV } = $ name ; # Name des aufrufenden SMAPortalSPG-Devices
$ 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)
2021-01-24 18:03:44 +00:00
my $ fcdev = ReadingsVal ( $ name , "currentForecastDev" , "" ) ; # aktuelles Forecast Device
my $ indev = ReadingsVal ( $ name , "currentInverterDev" , "" ) ; # aktuelles Inverter Device
2020-12-15 13:41:10 +00:00
my ( $ a , $ h ) = parseParams ( $ indev ) ;
2021-01-26 20:38:22 +00:00
$ indev = $ a - > [ 0 ] // "" ;
2020-12-13 17:29:15 +00:00
2021-03-18 22:12:08 +00:00
my $ pv0 = ReadingsNum ( $ name , "NextHour00_PVforecast" , undef ) ;
2020-12-13 17:29:15 +00:00
2021-01-26 20:38:22 +00:00
my $ is = ReadingsVal ( $ name , "inverterStrings" , undef ) ; # String Konfig
2021-01-27 17:02:18 +00:00
my $ peak = ReadingsVal ( $ name , "modulePeakString" , undef ) ; # String Peak
2021-01-26 20:38:22 +00:00
my $ dir = ReadingsVal ( $ name , "moduleDirection" , undef ) ; # Modulausrichtung Konfig
my $ ta = ReadingsVal ( $ name , "moduleTiltAngle" , undef ) ; # Modul Neigungswinkel Konfig
2021-01-27 17:02:18 +00:00
if ( ! $ is || ! $ fcdev || ! $ indev || ! $ peak || ! defined $ pv0 || ! $ dir || ! $ ta ) {
2021-01-26 20:38:22 +00:00
my $ link = qq{ <a href="/fhem?detail=$name">$name</a> } ;
$ height = AttrNum ( $ name , 'beamHeight' , 200 ) ;
$ ret . = "<table class='roomoverview'>" ;
$ ret . = "<tr style='height:" . $ height . "px'>" ;
$ ret . = "<td>" ;
2020-12-13 17:29:15 +00:00
if ( ! $ fcdev ) {
2021-01-26 20:38:22 +00:00
$ ret . = qq{ Please select a Solar Forecast device with "set $link currentForecastDev" } ;
2020-12-13 17:29:15 +00:00
}
elsif ( ! $ indev ) {
2021-01-26 20:38:22 +00:00
$ ret . = qq{ Please select an Inverter device with "set $link currentInverterDev" } ;
}
elsif ( ! $ is ) {
$ ret . = qq{ Please define all of your used string names with "set $link inverterStrings". } ;
2020-12-13 17:29:15 +00:00
}
2021-01-27 17:02:18 +00:00
elsif ( ! $ peak ) {
$ ret . = qq{ Please specify the total module peak with "set $link modulePeakString" } ;
2021-01-24 18:03:44 +00:00
}
2021-01-26 20:38:22 +00:00
elsif ( ! $ dir ) {
$ ret . = qq{ Please specify the module direction with "set $link moduleDirection" } ;
}
elsif ( ! $ ta ) {
$ ret . = qq{ Please specify the module tilt angle with "set $link moduleTiltAngle" } ;
2021-01-24 20:06:33 +00:00
}
2020-12-13 17:29:15 +00:00
elsif ( ! defined $ pv0 ) {
$ ret . = qq{ Awaiting data from selected Solar Forecast device ... } ;
}
$ ret . = "</td>" ;
$ ret . = "</tr>" ;
$ ret . = "</table>" ;
return $ ret ;
}
2021-01-26 20:38:22 +00:00
my $ cclv = "L05" ;
2021-03-16 14:53:54 +00:00
my @ pgCDev = split ( ',' , AttrVal ( $ name , "consumerList" , "" ) ) ; # definierte Verbraucher ermitteln
my ( $ legend_style , $ legend ) = split ( '_' , AttrVal ( $ name , 'consumerLegend' , 'icon_top' ) ) ;
2020-12-13 17:29:15 +00:00
$ legend = '' if ( ( $ legend_style eq 'none' ) || ( ! int ( @ pgCDev ) ) ) ;
###################################
# Verbraucherlegende und Steuerung
###################################
my $ legend_txt ;
if ( $ legend ) {
for ( @ pgCDev ) {
my ( $ txt , $ im ) = split ( ':' , $ _ ) ; # $txt ist der Verbrauchername
my $ cmdon = "\"FW_cmd('$FW_ME$FW_subdir?XHR=1&cmd=set $name $txt on')\"" ;
my $ cmdoff = "\"FW_cmd('$FW_ME$FW_subdir?XHR=1&cmd=set $name $txt off')\"" ;
my $ cmdauto = "\"FW_cmd('$FW_ME$FW_subdir?XHR=1&cmd=set $name $txt auto')\"" ;
if ( $ ftui eq "ftui" ) {
$ cmdon = "\"ftui.setFhemStatus('set $name $txt on')\"" ;
$ cmdoff = "\"ftui.setFhemStatus('set $name $txt off')\"" ;
$ cmdauto = "\"ftui.setFhemStatus('set $name $txt auto')\"" ;
}
my $ swstate = ReadingsVal ( $ name , "${cclv}_" . $ txt . "_Switch" , "undef" ) ;
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>" ;
}
elsif ( $ swstate eq "on" ) {
$ swicon = "<a onClick=$cmdauto><img src=\"$FW_ME/www/images/default/10px-kreis-gruen.png\"></a>" ;
}
elsif ( $ swstate =~ /off.*automatic.*/ix ) {
$ swicon = "<a onClick=$cmdon><img src=\"$FW_ME/www/images/default/10px-kreis-gelb.png\"></a>" ;
}
if ( $ legend_style eq 'icon' ) { # mögliche Umbruchstellen mit normalen Blanks vorsehen !
$ legend_txt . = $ txt . ' ' . FW_makeImage ( $ im ) . ' ' . $ swicon . ' ' ;
}
else {
my ( undef , $ co ) = split ( '\@' , $ im ) ;
$ co = '#cccccc' if ( ! $ co ) ; # Farbe per default
$ legend_txt . = '<font color=\'' . $ co . '\'>' . $ txt . '</font> ' . $ swicon . ' ' ; # hier auch Umbruch erlauben
}
}
}
###################################
# Parameter f. Anzeige extrahieren
###################################
my $ maxhours = AttrNum ( $ name , 'hourCount' , 24 ) ;
2021-03-16 14:53:54 +00:00
my $ offset = AttrNum ( $ name , 'history_hour' , 0 ) ;
2020-12-13 17:29:15 +00:00
my $ hourstyle = AttrVal ( $ name , 'hourStyle' , undef ) ;
2021-03-16 14:53:54 +00:00
my $ colorfc = AttrVal ( $ name , 'beam1Color' , '000000' ) ;
my $ colorc = AttrVal ( $ name , 'beam2Color' , 'C4C4A7' ) ;
2021-03-14 11:51:38 +00:00
my $ beam1cont = AttrVal ( $ name , 'beam1Content' , 'forecast' ) ;
my $ beam2cont = AttrVal ( $ name , 'beam2Content' , 'forecast' ) ;
2021-03-16 14:53:54 +00:00
2020-12-13 17:29:15 +00:00
my $ icon = AttrVal ( $ name , 'consumerAdviceIcon' , undef ) ;
my $ html_start = AttrVal ( $ name , 'htmlStart' , undef ) ; # beliebige HTML Strings die vor der Grafik ausgegeben werden
my $ html_end = AttrVal ( $ name , 'htmlEnd' , undef ) ; # beliebige HTML Strings die nach der Grafik ausgegeben werden
2021-03-16 14:53:54 +00:00
my $ lotype = AttrVal ( $ name , 'layoutType' , 'pv' ) ;
2020-12-13 17:29:15 +00:00
my $ kw = AttrVal ( $ name , 'Wh/kWh' , 'Wh' ) ;
$ height = AttrNum ( $ name , 'beamHeight' , 200 ) ;
my $ width = AttrNum ( $ name , 'beamWidth' , 6 ) ; # zu klein ist nicht problematisch
my $ w = $ width * $ maxhours ; # gesammte Breite der Ausgabe , WetterIcon braucht ca. 34px
my $ fsize = AttrNum ( $ name , 'spaceSize' , 24 ) ;
my $ maxVal = AttrNum ( $ name , 'maxPV' , 0 ) ; # dyn. Anpassung der Balkenhöhe oder statisch ?
my $ show_night = AttrNum ( $ name , 'showNight' , 0 ) ; # alle Balken (Spalten) anzeigen ?
my $ show_diff = AttrVal ( $ name , 'showDiff' , 'no' ) ; # zusätzliche Anzeige $di{} in allen Typen
my $ weather = AttrNum ( $ name , 'showWeather' , 1 ) ;
2021-03-16 14:53:54 +00:00
my $ colorw = AttrVal ( $ name , 'weatherColor' , 'FFFFFF' ) ; # Wetter Icon Farbe
2021-01-01 19:44:20 +00:00
my $ colorwn = AttrVal ( $ name , 'weatherColor_night' , $ colorw ) ; # Wetter Icon Farbe Nacht
2020-12-13 17:29:15 +00:00
my $ wlalias = AttrVal ( $ name , 'alias' , $ name ) ;
my $ header = AttrNum ( $ name , 'showHeader' , 1 ) ;
my $ hdrAlign = AttrVal ( $ name , 'headerAlignment' , 'center' ) ; # ermöglicht per attr die Ausrichtung der Tabelle zu setzen
my $ hdrDetail = AttrVal ( $ name , 'headerDetail' , 'all' ) ; # ermöglicht den Inhalt zu begrenzen, um bspw. passgenau in ftui einzubetten
# Icon Erstellung, mit @<Farbe> ergänzen falls einfärben
# Beispiel mit Farbe: $icon = FW_makeImage('light_light_dim_100.svg@green');
$ icon = FW_makeImage ( $ icon ) if ( defined ( $ icon ) ) ;
my $ co4h = ReadingsNum ( $ name , "Next04Hours_Consumption" , 0 ) ;
my $ coRe = ReadingsNum ( $ name , "RestOfDay_Consumption" , 0 ) ;
my $ coTo = ReadingsNum ( $ name , "Tomorrow_Consumption" , 0 ) ;
my $ coCu = ReadingsNum ( $ name , "Current_GridConsumption" , 0 ) ;
my $ pv4h = ReadingsNum ( $ name , "Next04Hours_PV" , 0 ) ;
my $ pvRe = ReadingsNum ( $ name , "RestOfDay_PV" , 0 ) ;
2021-03-18 22:30:51 +00:00
my $ pvTo = ReadingsNum ( $ name , "Tomorrow_PVforecast" , 0 ) ;
2020-12-13 17:29:15 +00:00
my $ pvCu = ReadingsNum ( $ name , "Current_PV" , 0 ) ;
2020-12-25 14:14:14 +00:00
my $ pcfa = ReadingsVal ( $ name , "pvCorrectionFactor_Auto" , "off" ) ;
2020-12-13 17:29:15 +00:00
if ( $ kw eq 'kWh' ) {
$ co4h = sprintf ( "%.1f" , $ co4h / 1000 ) . " kWh" ;
$ coRe = sprintf ( "%.1f" , $ coRe / 1000 ) . " kWh" ;
$ coTo = sprintf ( "%.1f" , $ coTo / 1000 ) . " kWh" ;
$ coCu = sprintf ( "%.1f" , $ coCu / 1000 ) . " kW" ;
$ pv4h = sprintf ( "%.1f" , $ pv4h / 1000 ) . " kWh" ;
$ pvRe = sprintf ( "%.1f" , $ pvRe / 1000 ) . " kWh" ;
$ pvTo = sprintf ( "%.1f" , $ pvTo / 1000 ) . " kWh" ;
$ pvCu = sprintf ( "%.1f" , $ pvCu / 1000 ) . " kW" ;
}
else {
$ co4h . = " Wh" ;
$ coRe . = " Wh" ;
$ coTo . = " Wh" ;
$ coCu . = " W" ;
$ pv4h . = " Wh" ;
$ pvRe . = " Wh" ;
$ pvTo . = " Wh" ;
$ pvCu . = " W" ;
}
##########################
# Headerzeile generieren
##########################
if ( $ header ) {
2021-03-16 14:53:54 +00:00
my $ lang = AttrVal ( "global" , "language" , "EN" ) ;
my $ alias = AttrVal ( $ name , "alias" , $ name ) ; # Linktext als Aliasname
2020-12-13 17:29:15 +00:00
my $ dlink = "<a href=\"/fhem?detail=$name\">$alias</a>" ;
2021-03-20 09:05:26 +00:00
my $ lup = ReadingsTimestamp ( $ name , "NextHour00_PVforecast" , "0000-00-00 00:00:00" ) ; # letzter Forecast Update
2020-12-13 17:29:15 +00:00
2020-12-25 14:14:14 +00:00
my $ lupt = "last update:" ;
my $ autoct = "automatic correction:" ;
2020-12-13 17:29:15 +00:00
my $ lblPv4h = "next 4h:" ;
my $ lblPvRe = "remain today:" ;
my $ lblPvTo = "tomorrow:" ;
my $ lblPvCu = "actual" ;
if ( AttrVal ( "global" , "language" , "EN" ) eq "DE" ) { # Header globales Sprachschema Deutsch
2020-12-25 14:14:14 +00:00
$ lupt = "Stand:" ;
$ autoct = "automatische Korrektur:" ;
2020-12-13 17:29:15 +00:00
$ lblPv4h = encode ( "utf8" , "nächste 4h:" ) ;
2020-12-31 09:09:49 +00:00
$ lblPvRe = "Rest heute:" ;
2020-12-13 17:29:15 +00:00
$ lblPvTo = "morgen:" ;
$ lblPvCu = "aktuell" ;
}
$ header = "<table align=\"$hdrAlign\">" ;
#########################################
# Header Link + Status + Update Button
if ( $ hdrDetail eq "all" || $ hdrDetail eq "statusLink" ) {
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" ;
}
else {
$ lup = "$year-$month-$day $time" ;
}
2020-12-15 13:41:10 +00:00
my $ cmdupdate = "\"FW_cmd('$FW_ME$FW_subdir?XHR=1&cmd=get $name data')\"" ; # Update Button generieren
2020-12-13 17:29:15 +00:00
if ( $ ftui eq "ftui" ) {
2020-12-15 13:41:10 +00:00
$ cmdupdate = "\"ftui.setFhemStatus('get $name data')\"" ;
2020-12-13 17:29:15 +00:00
}
my $ upstate = ReadingsVal ( $ name , "state" , "" ) ;
2020-12-25 14:14:14 +00:00
## Update-Icon
##############
my $ upicon ;
2020-12-13 17:29:15 +00:00
if ( $ upstate =~ /updated/ix ) {
$ upicon = "<a onClick=$cmdupdate><img src=\"$FW_ME/www/images/default/10px-kreis-gruen.png\"></a>" ;
}
2020-12-15 13:41:10 +00:00
elsif ( $ upstate =~ /running/ix ) {
2020-12-13 17:29:15 +00:00
$ upicon = "<img src=\"$FW_ME/www/images/default/10px-kreis-gelb.png\"></a>" ;
}
elsif ( $ upstate =~ /initialized/ix ) {
$ upicon = "<img src=\"$FW_ME/www/images/default/1px-spacer.png\"></a>" ;
}
else {
$ upicon = "<a onClick=$cmdupdate><img src=\"$FW_ME/www/images/default/10px-kreis-rot.png\"></a>" ;
}
2020-12-25 14:14:14 +00:00
## Autokorrektur-Icon
######################
my $ acicon ;
if ( $ pcfa eq "on" ) {
$ acicon = "<img src=\"$FW_ME/www/images/default/10px-kreis-gruen.png\">" ;
}
elsif ( $ pcfa eq "off" ) {
$ acicon = "off" ;
}
elsif ( $ pcfa =~ /standby/ix ) {
my ( $ rtime ) = $ pcfa =~ /for (.*?) hours/x ;
$ acicon = "<img src=\"$FW_ME/www/images/default/10px-kreis-gelb.png\"> (Start in " . $ rtime . " h)" ;
}
else {
$ acicon = "<img src=\"$FW_ME/www/images/default/10px-kreis-rot.png\">" ;
}
2020-12-13 17:29:15 +00:00
2020-12-25 14:14:14 +00:00
## erste Header-Zeilen
#######################
$ header . = "<tr><td colspan=\"3\" align=\"left\"><b>" . $ dlink . "</b></td><td colspan=\"3\" align=\"left\">" . $ lupt . " " . $ lup . " " . $ upicon . "</td></tr>" ;
$ header . = "<tr><td colspan=\"3\" align=\"left\"><b> </b></td><td colspan=\"3\" align=\"left\">" . $ autoct . " " . $ acicon . "</td></tr>" ;
2020-12-13 17:29:15 +00:00
}
########################
# Header Information pv
if ( $ hdrDetail eq "all" || $ hdrDetail eq "pv" || $ hdrDetail eq "pvco" ) {
$ header . = "<tr>" ;
$ header . = "<td><b>PV =></b></td>" ;
$ header . = "<td><b>$lblPvCu</b></td> <td align=right>$pvCu</td>" ;
$ 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>" ;
}
########################
# Header Information co
if ( $ hdrDetail eq "all" || $ hdrDetail eq "co" || $ hdrDetail eq "pvco" ) {
$ header . = "<tr>" ;
$ header . = "<td><b>CO =></b></td>" ;
$ header . = "<td><b>$lblPvCu</b></td> <td align=right>$coCu</td>" ;
$ 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>" ;
}
$ header . = "</table>" ;
}
2021-03-14 11:51:38 +00:00
##########################
# Werte aktuelle Stunde
##########################
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
my $ thishour ;
2021-03-14 11:51:38 +00:00
2021-03-18 22:12:08 +00:00
( undef , undef , undef , $ thishour ) = ReadingsVal ( $ name , "NextHour00_Time" , '0000-00-00 24' ) =~ m/(\d{4})-(\d{2})-(\d{2})\s(\d{2})/x ;
( undef , undef , undef , $ thishour ) = ReadingsVal ( $ name , "NextHour00_Time" , '00.00.0000 24' ) =~ m/(\d{2}).(\d{2}).(\d{4})\s(\d{2})/x if ( AttrVal ( 'global' , 'language' , '' ) eq 'DE' ) ;
2021-03-16 14:53:54 +00:00
$ thishour = int ( $ thishour ) ; # keine führende Null
$ t { 0 } = $ thishour ;
2021-03-14 11:51:38 +00:00
2021-03-16 14:53:54 +00:00
my $ val1 ;
my $ val2 ;
2021-03-14 11:51:38 +00:00
if ( $ offset ) {
2021-03-20 13:55:15 +00:00
$ t { 0 } += $ offset ;
$ t { 0 } += 24 if ( $ t { 0 } < 0 ) ;
2021-03-21 12:25:45 +00:00
my $ t0 = sprintf ( '%02d' , $ t { 0 } ) ; # Index liegt eins höher : 10:00 = Index '11'
2021-03-20 13:55:15 +00:00
$ val1 = ( exists ( $ data { $ hash - > { TYPE } } { $ name } { pvfc } { $ t0 } ) ) ? $ data { $ hash - > { TYPE } } { $ name } { pvfc } { $ t0 } : 0 ;
$ val2 = ( exists ( $ data { $ hash - > { TYPE } } { $ name } { pvreal } { $ t0 } ) ) ? $ data { $ hash - > { TYPE } } { $ name } { pvreal } { $ t0 } : 0 ;
$ we { 0 } = ( exists ( $ data { $ hash - > { TYPE } } { $ name } { weather } { $ t0 } { id } ) ) ? $ data { $ hash - > { TYPE } } { $ name } { weather } { $ t0 } { id } : - 1 ;
#$is{0} = undef;
2021-03-14 11:51:38 +00:00
}
else {
2021-03-21 12:25:45 +00:00
my $ t0 = sprintf ( '%02d' , $ t { 0 } + 1 ) ;
2021-03-20 13:55:15 +00:00
$ val1 = ( exists ( $ data { $ hash - > { TYPE } } { $ name } { pvfc } { $ t0 } ) ) ? $ data { $ hash - > { TYPE } } { $ name } { pvfc } { $ t0 } : 0 ;
$ val2 = ( exists ( $ data { $ hash - > { TYPE } } { $ name } { pvreal } { $ t0 } ) ) ? $ data { $ hash - > { TYPE } } { $ name } { pvreal } { $ t0 } : 0 ;
# ToDo : klären ob ThisHour:weather_Id stimmt in Bezug zu ThisHour_Time
$ we { 0 } = ( exists ( $ hash - > { HELPER } { 'NextHour00_WeatherId' } ) ) ? $ hash - > { HELPER } { "NextHour00_WeatherId" } : - 1 ;
#$is{0} = (ReadingsVal($name,"NextHour00_IsConsumptionRecommended",'no') eq 'yes' ) ? $icon : undef;
2021-03-14 11:51:38 +00:00
}
2021-03-16 14:53:54 +00:00
$ beam1 { 0 } = ( $ beam1cont eq 'forecast' ) ? $ val1 : $ val2 ;
$ beam2 { 0 } = ( $ beam2cont eq 'forecast' ) ? $ val1 : $ val2 ;
$ di { 0 } = $ beam1 { 0 } - $ beam2 { 0 } ;
2021-03-14 11:51:38 +00:00
# User Auswahl überschreiben wenn beide Werte die gleiche Basis haben !
$ lotype = 'pv' if ( $ beam1cont eq $ beam2cont ) ;
###########################################################
# get consumer list and display it in portalGraphics
###########################################################
2021-03-16 14:53:54 +00:00
for ( @ pgCDev ) {
my ( $ itemName , undef ) = split ( ':' , $ _ ) ;
$ itemName =~ s/^\s+|\s+$//gx ; #trim it, if blanks were used
$ _ =~ s/^\s+|\s+$//gx ; #trim it, if blanks were used
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
##################################
#check if listed device is planned
if ( ReadingsVal ( $ name , $ itemName . "_Planned" , "no" ) eq "yes" ) {
#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
2021-03-14 11:51:38 +00:00
2021-03-16 14:53:54 +00:00
if ( AttrVal ( "global" , "language" , "EN" ) eq "DE" ) {
( undef , undef , undef , $ start ) = ReadingsVal ( $ name , $ itemName . "_PlannedOpTimeBegin" , '00.00.0000 24' ) =~ m/(\d{2}).(\d{2}).(\d{4})\s(\d{2})/x ;
( undef , undef , undef , $ end ) = ReadingsVal ( $ name , $ itemName . "_PlannedOpTimeEnd" , '00.00.0000 24' ) =~ m/(\d{2}).(\d{2}).(\d{4})\s(\d{2})/x ;
}
else {
( undef , undef , undef , $ start ) = ReadingsVal ( $ name , $ itemName . "_PlannedOpTimeBegin" , '0000-00-00 24' ) =~ m/(\d{4})-(\d{2})-(\d{2})\s(\d{2})/x ;
( undef , undef , undef , $ end ) = ReadingsVal ( $ name , $ itemName . "_PlannedOpTimeEnd" , '0000-00-00 24' ) =~ m/(\d{4})-(\d{2})-(\d{2})\s(\d{2})/x ;
}
2021-03-14 11:51:38 +00:00
2021-03-16 14:53:54 +00:00
$ start = int ( $ start ) ;
$ end = int ( $ end ) ;
my $ flag = 0 ; # default kein Tagesverschieber
2021-03-14 11:51:38 +00:00
2021-03-16 14:53:54 +00:00
#######################################
#correct the hour for accurate display
if ( $ start < $ t { 0 } ) { # consumption seems to be tomorrow
$ start = 24 - $ t { 0 } + $ start ;
$ flag = 1 ;
}
else {
$ start -= $ t { 0 } ;
}
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
if ( $ flag ) { # consumption seems to be tomorrow
$ end = 24 - $ t { 0 } + $ end ;
}
else {
$ end -= $ t { 0 } ;
}
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
$ _ . = ":" . $ start . ":" . $ end ;
}
else {
$ _ . = ":24:24" ;
}
Log3 ( $ name , 4 , "$name - Consumer planned data: $_" ) ;
2020-12-13 17:29:15 +00:00
}
2021-03-16 14:53:54 +00:00
$ maxVal = ! $ maxVal ? $ beam1 { 0 } : $ maxVal ; # Startwert wenn kein Wert bereits via attr vorgegeben ist
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
my $ maxCon = $ beam2 { 0 } ; # für Typ co
my $ maxDif = $ di { 0 } ; # für Typ diff
my $ minDif = $ di { 0 } ; # für Typ diff
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
for my $ i ( 1 .. ( $ maxhours * 2 ) - 1 ) { # doppelte Anzahl berechnen
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
my $ val1 ;
my $ val2 = 0 ;
2021-03-20 21:26:12 +00:00
my $ ii = sprintf ( '%02d' , $ i ) ;
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
$ t { $ i } = $ thishour + $ i ;
$ t { $ i } -= 24 if ( $ t { $ i } > 23 ) ;
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
if ( $ offset < 0 ) {
$ t { $ i } += $ offset ;
$ t { $ i } += 24 if ( $ t { $ i } < 0 ) ;
2021-03-14 11:51:38 +00:00
2021-03-16 14:53:54 +00:00
my $ jj = sprintf ( '%02d' , $ t { $ i } ) ;
2021-03-14 11:51:38 +00:00
2021-03-16 14:53:54 +00:00
if ( $ i <= abs ( $ offset ) ) {
2021-03-21 11:50:35 +00:00
$ val1 = ( exists ( $ data { $ hash - > { TYPE } } { $ name } { pvfc } { $ jj } ) ) ? $ data { $ hash - > { TYPE } } { $ name } { pvfc } { $ jj } : 0 ;
$ val2 = ( exists ( $ data { $ hash - > { TYPE } } { $ name } { pvreal } { $ jj } ) ) ? $ data { $ hash - > { TYPE } } { $ name } { pvreal } { $ jj } : 0 ;
$ we { $ i } = ( exists ( $ data { $ hash - > { TYPE } } { $ name } { weather } { $ jj } { id } ) ) ? $ data { $ hash - > { TYPE } } { $ name } { weather } { $ jj } { id } : - 1 ;
2021-03-16 14:53:54 +00:00
}
else {
2021-03-21 11:50:35 +00:00
my $ nh = sprintf ( '%02d' , $ i + $ offset ) ;
2021-03-20 13:55:15 +00:00
$ val1 = ReadingsNum ( $ name , 'NextHour' . $ nh . '_PVforecast' , 0 ) ;
# ToDo : klären ob -1 oder nicht !
#$nh = sprintf('%02d', $i+$offset-1);
$ we { $ i } = ( exists ( $ hash - > { HELPER } { 'NextHour' . $ nh . '_WeatherId' } ) ) ? $ hash - > { HELPER } { 'NextHour' . $ nh . '_WeatherId' } : - 1 ;
2021-03-16 14:53:54 +00:00
}
}
else {
$ val1 = ReadingsNum ( $ name , 'NextHour' . $ ii . '_PVforecast' , 0 ) ; # Forecast
$ we { $ i } = ( exists ( $ hash - > { HELPER } { 'NextHour' . $ ii . '_WeatherId' } ) ) ? $ hash - > { HELPER } { 'NextHour' . $ ii . '_WeatherId' } : - 1 ; # für Wettericons
#$is{$i} = (ReadingsVal($name,"NextHour".$ii."_IsConsumptionRecommended",'no') eq 'yes') ? $icon : undef;
}
2021-03-14 11:51:38 +00:00
2021-03-16 14:53:54 +00:00
$ beam1 { $ i } = ( $ beam1cont eq 'forecast' ) ? $ val1 : $ val2 ;
$ beam2 { $ i } = ( $ beam2cont eq 'forecast' ) ? $ val1 : $ val2 ;
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
# sicher stellen das wir keine undefs in der Liste haben !
$ beam1 { $ i } // = 0 ;
$ beam2 { $ i } // = 0 ;
$ di { $ i } = $ beam1 { $ i } - $ beam2 { $ i } ;
$ we { $ i } // = - 1 ;
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
$ maxVal = $ beam1 { $ i } if ( $ beam1 { $ i } > $ maxVal ) ;
$ maxCon = $ beam2 { $ i } if ( $ beam2 { $ i } > $ maxCon ) ;
$ maxDif = $ di { $ i } if ( $ di { $ i } > $ maxDif ) ;
$ minDif = $ di { $ i } if ( $ di { $ i } < $ minDif ) ;
}
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +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
$ ret = "<html>" ;
$ ret . = $ html_start if ( defined ( $ html_start ) ) ;
$ ret . = "<style>TD.smaportal {text-align: center; padding-left:1px; padding-right:1px; margin:0px;}</style>" ;
$ ret . = "<table class='roomoverview' width='$w' style='width:" . $ w . "px'><tr class='devTypeTr'></tr>" ;
$ ret . = "<tr><td class='smaportal'>" ;
$ ret . = "\n<table class='block'>" ; # das \n erleichtert das Lesen der debug Quelltextausgabe
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 ) {
$ ret . = "<tr class='even'><td class='smaportal'></td>" ; # freier Platz am Anfang
my $ ii ;
for my $ i ( 0 .. ( $ maxhours * 2 ) - 1 ) {
next if ( ! $ show_night && ( $ we { $ i } > 99 ) && ! $ beam1 { $ i } && ! $ beam2 { $ i } ) ;
# Lässt Nachticons aber noch durch wenn es einen Wert gibt , ToDo : klären ob die Nacht richtig gesetzt wurde
$ ii + + ; # wieviele Stunden haben wir bisher angezeigt ?
last if ( $ ii > $ maxhours ) ; # vorzeitiger Abbruch
# FHEM Wetter Icons (weather_xxx) , Skalierung und Farbe durch FHEM Bordmittel
# ToDo : weather_icon sollte im Fehlerfall Title mit der ID besetzen um in FHEMWEB sofort die ID sehen zu können
my ( $ icon_name , $ title ) = ( ( $ we { $ i } > 99 ) ) ? weather_icon ( $ we { $ i } - 100 ) : weather_icon ( $ we { $ i } ) ;
# unknown -> FHEM Icon Fragezeichen im Kreis wird als Ersatz Icon ausgegeben
Log3 ( $ name , 4 , "$name - unknown weather id: " . $ we { $ i } . ", please inform the maintainer" ) if ( $ icon_name eq 'unknown' ) ;
$ icon_name . = ( $ we { $ i } < 100 ) ? '@' . $ colorw : '@' . $ colorwn ;
$ val = FW_makeImage ( $ icon_name ) ;
if ( $ val eq $ icon_name ) { # passendes Icon beim User nicht vorhanden ! ( attr web iconPath falsch/prüfen/update ? )
$ val = '<b>???<b/>' ;
Log3 ( $ name , 3 , qq{ $name - the icon $we { $i } not found. Please check attribute "iconPath" of your FHEMWEB instance and/or update your FHEM software } ) ;
}
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
$ ret . = "<td title='$title' class='smaportal' width='$width' style='margin:1px; vertical-align:middle align:center; padding-bottom:1px;'>$val</td>" ; # title -> Mouse Over Text
# mit $we{$i} = undef kann man unten leicht feststellen ob für diese Spalte bereits ein Icon ausgegeben wurde oder nicht
# Todo : ist jetzt nicht so , prüfen da formatVal6 we undef auswertet
}
$ ret . = "<td class='smaportal'></td></tr>" ; # freier Platz am Ende der Icon Zeile
}
if ( $ show_diff eq 'top' ) { # Zusätzliche Zeile Ertrag - Verbrauch
$ ret . = "<tr class='even'><td class='smaportal'></td>" ; # freier Platz am Anfang
my $ ii ;
for my $ i ( 0 .. ( $ maxhours * 2 ) - 1 ) {
# gleiche Bedingung wie oben
next if ( ! $ show_night && ( $ we { $ i } > 99 ) && ! $ beam1 { $ i } && ! $ beam2 { $ i } ) ;
$ ii + + ; # wieviele Stunden haben wir bisher angezeigt ?
last if ( $ ii > $ maxhours ) ; # vorzeitiger Abbruch
$ val = formatVal6 ( $ di { $ i } , $ kw , $ we { $ i } ) ;
$ val = ( $ di { $ i } < 0 ) ? '<b>' . $ val . '<b/>' : ( $ val > 0 ) ? '+' . $ val : $ val ; # negative Zahlen in Fettschrift, 0 aber ohne +
$ ret . = "<td class='smaportal' style='vertical-align:middle; text-align:center;'>$val</td>" ;
}
$ ret . = "<td class='smaportal'></td></tr>" ; # freier Platz am Ende
}
$ ret . = "<tr class='even'><td class='smaportal'></td>" ; # Neue Zeile mit freiem Platz am Anfang
my $ ii = 0 ;
for my $ i ( 0 .. ( $ maxhours * 2 ) - 1 ) {
# gleiche Bedingung wie oben
next if ( ! $ show_night && ( $ we { $ i } > 99 ) && ! $ beam1 { $ i } && ! $ beam2 { $ i } ) ;
$ ii + + ;
last if ( $ ii > $ maxhours ) ;
# Achtung Falle, Division by Zero möglich,
# maxVal kann gerade bei kleineren maxhours Ausgaben in der Nacht leicht auf 0 fallen
$ height = 200 if ( ! $ height ) ; # Fallback, sollte eigentlich nicht vorkommen, außer der User setzt es auf 0
$ maxVal = 1 if ( ! int $ maxVal ) ;
$ maxCon = 1 if ( ! $ maxCon ) ;
# Der zusätzliche Offset durch $fsize verhindert bei den meisten Skins
# dass die Grundlinie der Balken nach unten durchbrochen wird
if ( $ lotype eq 'pv' ) {
$ he = int ( ( $ maxVal - $ beam1 { $ i } ) / $ maxVal * $ height ) + $ fsize ;
$ z3 = int ( $ height + $ fsize - $ he ) ;
}
if ( $ lotype eq 'pvco' ) {
# 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 ( $ beam1 { $ i } > $ beam2 { $ i } ) { # pv oben , co unten
$ z2 = $ beam1 { $ i } ; $ z3 = $ beam2 { $ i } ;
}
else { # tauschen, Verbrauch ist größer als Ertrag
$ z3 = $ beam1 { $ i } ; $ z2 = $ beam2 { $ i } ;
}
$ he = int ( ( $ maxVal - $ z2 ) / $ maxVal * $ height ) ;
$ z2 = int ( ( $ z2 - $ z3 ) / $ maxVal * $ height ) ;
$ z3 = int ( $ height - $ he - $ z2 ) ; # was von maxVal noch übrig ist
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
if ( $ z3 < int ( $ fsize /2)) { # dünnen Strichbalken vermeiden / ca . halbe Zeichenhöhe
$ z2 += $ z3 ; $ z3 = 0 ;
}
}
2021-03-14 11:51:38 +00:00
2021-03-16 14:53:54 +00:00
if ( $ lotype eq 'diff' ) { # Typ diff
# 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
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
my ( $ px_pos , $ px_neg ) ;
my $ maxPV = 0 ; # ToDo: maxPV noch aus Attribut maxPV ableiten
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
if ( $ maxPV ) { # Feste Aufteilung +/- , jeder 50 % bei maxPV = 0
$ px_pos = int ( $ height / 2 ) ;
$ px_neg = $ height - $ px_pos ; # Rundungsfehler vermeiden
}
else { # Dynamische hoch/runter Verschiebung der Null-Linie
if ( $ minDif >= 0 ) { # keine negativen Balken vorhanden, die Positiven bekommen den gesammten Raum
$ px_neg = 0 ;
$ px_pos = $ height ;
}
else {
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
}
else { # keine positiven Balken vorhanden, die Negativen bekommen den gesammten Raum
$ px_neg = $ height ;
$ px_pos = 0 ;
}
}
}
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
if ( $ di { $ i } >= 0 ) { # Zone 2 & 3 mit ihren direkten Werten vorbesetzen
$ z2 = $ di { $ i } ;
$ z3 = abs ( $ minDif ) ;
}
else {
$ z2 = $ maxDif ;
$ z3 = abs ( $ di { $ i } ) ; # Nur Betrag ohne Vorzeichen
}
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
# Alle vorbesetzen Werte umrechnen auf echte Ausgabe px
$ he = ( ! $ px_pos || ! $ maxDif ) ? 0 : int ( ( $ maxDif - $ z2 ) / $ maxDif * $ px_pos ) ; # Teilung durch 0 vermeiden
$ z2 = ( $ px_pos - $ he ) ;
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
$ z4 = ( ! $ px_neg || ! $ minDif ) ? 0 : int ( ( abs ( $ minDif ) - $ z3 ) / abs ( $ minDif ) * $ px_neg ) ; # Teilung durch 0 unbedingt vermeiden
$ z3 = ( $ px_neg - $ z4 ) ;
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
# 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
}
# 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
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
$ ret . = "<td style='text-align: center; padding-left:1px; padding-right:1px; margin:0px; vertical-align:bottom; padding-top:0px'>\n" ;
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
if ( $ lotype eq 'pv' ) {
#my $v = ($lotype eq 'co') ? $beam2{$i} : $beam1{$i} ;
#$v = 0 if (($lotype eq 'co') && !$beam1{$i} && !$show_night); # auch bei type co die Nacht ggf. unterdrücken
$ val = formatVal6 ( $ beam1 { $ i } , $ kw , $ we { $ i } ) ;
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
$ 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>" ;
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
if ( $ beam1 { $ i } || $ 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;" ;
$ style . = defined $ colorfc ? " background-color:#$colorfc\"" : '"' ; # Syntaxhilight
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
$ ret . = "<tr class='odd' style='height:" . $ z3 . "px;'>" ;
$ ret . = "<td align='center' class='smaportal' " . $ style . ">" ;
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
my $ sicon = 1 ;
$ ret . = $ is { $ i } if ( defined ( $ is { $ i } ) && $ sicon ) ;
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
##################################
# inject the new icon if defined
#$ret .= consinject($hash,$i,@pgCDev) if($s);
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
$ ret . = "</td></tr>" ;
}
}
2021-03-14 11:51:38 +00:00
2021-03-16 14:53:54 +00:00
if ( $ lotype eq 'pvco' ) {
my ( $ color1 , $ color2 , $ style1 , $ style2 , $ v ) ;
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
$ ret . = "<table width='100%' height='100%'>\n" ; # mit width=100% etwas bessere Füllung der Balken
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
# der Freiraum oben kann beim größten Balken ganz entfallen
$ ret . = "<tr class='even' style='height:" . $ he . "px'><td class='smaportal'></td></tr>" if ( $ he ) ;
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
if ( $ beam1 { $ i } > $ beam2 { $ i } ) { # wer ist oben, co pder pv ? Wert und Farbe für Zone 2 & 3 vorbesetzen
$ val = formatVal6 ( $ beam1 { $ i } , $ kw , $ we { $ i } ) ;
$ color1 = $ colorfc ;
$ style1 = "style=\"padding-bottom:0px; padding-top:1px; vertical-align:top; margin-left:auto; margin-right:auto;" ;
$ style1 . = ( defined ( $ color1 ) ) ? " background-color:#$color1\"" : '"' ;
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
if ( $ z3 ) { # die Zuweisung können wir uns sparen wenn Zone 3 nachher eh nicht ausgegeben wird
$ v = formatVal6 ( $ beam2 { $ 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\"" : '"' ;
}
}
else {
$ val = formatVal6 ( $ beam2 { $ 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\"" : '"' ;
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
if ( $ z3 ) {
$ v = formatVal6 ( $ beam1 { $ i } , $ kw , $ we { $ i } ) ;
$ color2 = $ colorfc ;
$ style2 = "style=\"padding-bottom:0px; padding-top:1px; vertical-align:top; margin-left:auto; margin-right:auto;" ;
$ style2 . = ( defined ( $ color2 ) ) ? " background-color:#$color2\"" : '"' ;
}
}
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
$ ret . = "<tr class='odd' style='height:" . $ z2 . "px'>" ;
$ ret . = "<td align='center' class='smaportal' " . $ style1 . ">$val" ;
#$ret .= $is{$i} if (defined $is{$i});
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
##################################
# inject the new icon if defined
#$ret .= consinject($hash,$i,@pgCDev) if($s);
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
$ ret . = "</td></tr>" ;
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
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>" ;
}
}
2021-03-14 11:51:38 +00:00
2021-03-16 14:53:54 +00:00
if ( $ lotype eq 'diff' ) { # Type diff
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}) : '';
2021-03-14 11:51:38 +00:00
2021-03-16 14:53:54 +00:00
$ val = ( $ di { $ i } > 0 ) ? formatVal6 ( $ di { $ i } , $ kw , $ we { $ i } ) : '' ;
$ val = ' 0 ' if ( $ di { $ i } == 0 ) ; # Sonderfall , hier wird die 0 gebraucht !
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
if ( $ val ) {
$ ret . = "<tr class='even' style='height:" . $ he . "px'>" ;
$ ret . = "<td class='smaportal' style='vertical-align:bottom'>" . $ val . "</td></tr>" ;
}
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
if ( $ di { $ i } >= 0 ) { # mit Farbe 1 colorfc füllen
$ style . = defined $ colorfc ? " background-color:#$colorfc\"" : '"' ;
$ 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>" ;
}
else { # ohne Farbe
$ 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>" ;
}
}
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
if ( $ di { $ i } < 0 ) { # Negativ Balken anzeigen ?
$ style . = ( defined ( $ colorc ) ) ? " background-color:#$colorc\"" : '"' ; # mit Farbe 2 colorc füllen
$ ret . = "<tr class='odd' style='height:" . $ z3 . "px'>" ;
$ ret . = "<td align='center' class='smaportal' " . $ style . "></td></tr>" ;
}
elsif ( $ z3 ) { # ohne Farbe
$ ret . = "<tr class='even' style='height:" . $ z3 . "px'>" ;
$ ret . = "<td class='smaportal'></td></tr>" ;
}
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
if ( $ z4 ) { # kann entfallen wenn auch z3 0 ist
$ val = ( $ di { $ i } < 0 ) ? formatVal6 ( $ di { $ i } , $ kw , $ we { $ i } ) : ' ' ;
$ ret . = "<tr class='even' style='height:" . $ z4 . "px'>" ;
$ ret . = "<td class='smaportal' style='vertical-align:top'>" . $ val . "</td></tr>" ;
}
}
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
if ( $ show_diff eq 'bottom' ) { # zusätzliche diff Anzeige
$ val = formatVal6 ( $ di { $ i } , $ kw , $ we { $ i } ) ;
$ val = ( $ di { $ i } < 0 ) ? '<b>' . $ val . '<b/>' : ( $ val > 0 ) ? '+' . $ val : $ val ; # negative Zahlen in Fettschrift, 0 aber ohne +
$ ret . = "<tr class='even'><td class='smaportal' style='vertical-align:middle; text-align:center;'>$val</td></tr>" ;
}
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
$ ret . = "<tr class='even'><td class='smaportal' style='vertical-align:bottom; text-align:center;'>" ;
$ t { $ i } = $ t { $ i } . $ hourstyle if ( defined ( $ hourstyle ) ) ; # z.B. 10:00 statt 10
$ ret . = ( ( $ t { $ i } == $ thishour ) && ( $ offset < 0 ) ) ? '<a class="changed" style="visibility:visible"><span>' . $ t { $ i } . '</span></a>' : $ t { $ i } ;
$ thishour = 24 if ( $ t { $ i } == $ thishour ) ; # nur einmal verwenden !
$ ret . = "</td></tr></table></td>" ;
} ## for i
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +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 ) ) ;
$ ret . = "</html>" ;
return $ ret ;
2020-12-13 17:29:15 +00:00
}
################################################################
# Inject consumer icon
################################################################
sub consinject {
my ( $ hash , $ i , @ pgCDev ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ ret = "" ;
for ( @ pgCDev ) {
if ( $ _ ) {
my ( $ cons , $ im , $ start , $ end ) = split ( ':' , $ _ ) ;
Log3 ( $ name , 4 , "$name - Consumer to show -> $cons, relative to current time -> start: $start, end: $end" ) if ( $ i < 1 ) ;
if ( $ im && ( $ i >= $ start ) && ( $ i <= $ end ) ) {
$ ret . = FW_makeImage ( $ im ) ;
}
}
}
return $ ret ;
}
###############################################################################
# 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
#
###############################################################################
sub formatVal6 {
2020-12-13 21:48:45 +00:00
my ( $ v , $ kw , $ w ) = @ _ ;
my $ n = ' ' ; # positive Zahl
2020-12-13 17:29:15 +00:00
2020-12-13 21:48:45 +00:00
if ( $ v < 0 ) {
2020-12-13 17:29:15 +00:00
$ n = '-' ; # negatives Vorzeichen merken
$ v = abs ( $ v ) ;
}
2020-12-13 21:48:45 +00:00
if ( $ kw eq 'kWh' ) { # bei Anzeige in kWh muss weniger aufgefüllt werden
2020-12-13 17:29:15 +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 ?
2020-12-13 21:48:45 +00:00
if ( ! $ t ) { # glatte Zahl ohne Nachkommastelle
2020-12-13 17:29:15 +00:00
if ( ! $ v ) {
return ' ' ; # 0 nicht anzeigen, passt eigentlich immer bis auf einen Fall im Typ diff
}
elsif ( $ v < 10 ) {
return ' ' . $ n . $ v . ' ' ;
}
else {
return ' ' . $ n . $ v . ' ' ;
}
}
else { # mit Nachkommastelle -> zwei Zeichen mehr .X
if ( $ v < 10 ) {
return ' ' . $ n . $ v . ' ' ;
}
else {
return $ n . $ v . ' ' ;
}
}
}
2020-12-13 21:48:45 +00:00
return ( $ n eq '-' ) ? ( $ v * - 1 ) : $ v if defined ( $ w ) ;
2020-12-13 17:29:15 +00:00
# Werte bleiben in Watt
if ( ! $ v ) { return ' ' ; } ## no critic "Cascading" # keine Anzeige bei Null
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
###############################################################################
sub weather_icon {
my $ id = shift ;
2021-01-17 19:13:02 +00:00
$ id = int $ id ;
2021-01-04 20:25:02 +00:00
if ( defined $ weather_ids { $ id } ) {
return $ weather_ids { $ id } { icon } , encode ( "utf8" , $ weather_ids { $ id } { txtd } ) ;
}
2020-12-13 17:29:15 +00:00
2021-01-04 20:25:02 +00:00
return 'unknown' , '' ;
2020-12-13 17:29:15 +00:00
}
################################################################
# Timestamp berechnen
################################################################
sub TimeAdjust {
my $ epoch = shift ;
my ( $ lyear , $ lmonth , $ lday , $ lhour ) = ( localtime ( $ epoch ) ) [ 5 , 4 , 3 , 2 ] ;
$ lyear += 1900 ; # year is 1900 based
$ lmonth + + ; # month number is zero based
if ( AttrVal ( "global" , "language" , "EN" ) eq "DE" ) {
return ( sprintf ( "%02d.%02d.%04d %02d:%s" , $ lday , $ lmonth , $ lyear , $ lhour , "00:00" ) ) ;
}
else {
return ( sprintf ( "%04d-%02d-%02d %02d:%s" , $ lyear , $ lmonth , $ lday , $ lhour , "00:00" ) ) ;
}
}
2021-01-23 11:46:54 +00:00
################################################################
# benötigte Attribute im DWD Device checken
################################################################
sub checkdwdattr {
my $ dwddev = shift ;
my @ fcprop = map { trim ( $ _ ) } split "," , AttrVal ( $ dwddev , "forecastProperties" , "pattern" ) ;
my @ aneeded ;
for my $ am ( @ dwdattrmust ) {
next if ( $ am ~ ~ @ fcprop ) ;
push @ aneeded , $ am ;
}
return @ aneeded ;
}
2020-12-13 17:29:15 +00:00
##################################################################################################
# PV Forecast Rad1h in kWh / Wh
2021-01-27 17:02:18 +00:00
# Berechnung nach Formel 1 aus http://www.ing-büro-junge.de/html/photovoltaik.html:
2020-12-13 17:29:15 +00:00
#
# * Faktor für Umwandlung kJ in kWh: 0.00027778
# * Eigene Modulfläche in qm z.B.: 31,04
# * Wirkungsgrad der Module in % z.B.: 16,52
# * Wirkungsgrad WR in % z.B.: 98,3
2021-01-27 17:02:18 +00:00
# * Korrekturwerte wegen Ausrichtung/Verschattung etc.
2020-12-13 17:29:15 +00:00
#
# Die Formel wäre dann:
2021-01-27 17:02:18 +00:00
# Ertrag in Wh = Rad1h * 0.00027778 * 31,04 qm * 16,52% * 98,3% * 100% * 1000
#
# Berechnung nach Formel 2 aus http://www.ing-büro-junge.de/html/photovoltaik.html:
2020-12-13 17:29:15 +00:00
#
2021-01-27 17:02:18 +00:00
# * Globalstrahlung: G = kJ / m2
# * Korrektur mit Flächenfaktor f: Gk = G * f
# * Globalstrahlung (STC): 1 kW/m2
# * Peak Leistung String (kWp): Pnenn = x kW
# * Performance Ratio: PR (typisch 0,85 bis 0,9)
# * weitere Korrekturwerte für Regen, Wolken etc.: Korr
2020-12-13 17:29:15 +00:00
#
2021-01-27 17:02:18 +00:00
# pv (kWh) = G * f * 0.00027778 (kWh/m2) / 1 kW/m2 * Pnenn (kW) * PR * Korr
# pv (Wh) = G * f * 0.00027778 (kWh/m2) / 1 kW/m2 * Pnenn (kW) * PR * Korr * 1000
2021-01-22 09:51:04 +00:00
#
# Die Abhängigkeit der Strahlungsleistung der Sonnenenergie nach Wetterlage und Jahreszeit ist
# hier beschrieben:
# https://www.energie-experten.org/erneuerbare-energien/photovoltaik/planung/sonnenstunden
#
2021-01-24 18:03:44 +00:00
# !!! PV Berechnungsgrundlagen !!!
# https://www.energie-experten.org/erneuerbare-energien/photovoltaik/planung/ertrag
# http://www.ing-büro-junge.de/html/photovoltaik.html
#
2020-12-13 17:29:15 +00:00
##################################################################################################
sub calcPVforecast {
my $ name = shift ;
2021-03-20 21:26:12 +00:00
my $ rad = shift ; # Nominale Strahlung aus DWD Device
my $ num = shift ; # Stunde des Tages
my $ t = shift ; # aktueller Unix Timestamp
2021-03-20 09:05:26 +00:00
my $ fd = shift ;
2020-12-13 17:29:15 +00:00
2021-01-21 22:07:18 +00:00
my $ hash = $ defs { $ name } ;
2021-01-26 20:38:22 +00:00
my $ type = $ hash - > { TYPE } ;
2021-03-20 21:26:12 +00:00
my $ stch = $ data { $ type } { $ name } { strings } ; # String Configuration Hash
my $ pr = 1.0 ; # Performance Ratio (PR)
2021-01-26 20:38:22 +00:00
2021-03-20 21:26:12 +00:00
my $ chour = strftime "%H" , localtime ( $ t + ( $ num * 3600 ) ) ; # aktuelle Stunde
2021-03-20 09:05:26 +00:00
my $ reld = $ fd == 0 ? "today" : $ fd == 1 ? "tomorrow" : "unknown" ;
2021-03-20 21:26:12 +00:00
my $ cloudslope = AttrVal ( $ name , "cloudFactorSlope" , $ clslopedef ) ; # prozentuale Berücksichtigung des Bewölkungskorrekturfaktors
my $ rainslope = AttrVal ( $ name , "rainFactorSlope" , $ rslopedef ) ; # prozentuale Berücksichtigung des Regenkorrekturfaktors
2021-03-20 13:55:15 +00:00
my @ strings = sort keys % { $ stch } ;
2021-01-26 20:38:22 +00:00
2021-03-20 21:26:12 +00:00
my $ cloudcover = $ data { $ type } { $ name } { nexthours } { "NextHour" . sprintf ( "%02d" , $ num ) } { cloudcover } // 0 ; # effektive Wolkendecke
my $ ccf = 1 - ( ( ( $ cloudcover - $ cloud_base ) /100) * $cloudslope/ 100 ) ; # Cloud Correction Faktor mit Steilheit und Fußpunkt
2021-01-26 20:38:22 +00:00
2021-03-20 21:26:12 +00:00
my $ rainprob = $ data { $ type } { $ name } { nexthours } { "NextHour" . sprintf ( "%02d" , $ num ) } { rainprob } // 0 ; # Niederschlagswahrscheinlichkeit> 0,1 mm während der letzten Stunde
my $ rcf = 1 - ( ( ( $ rainprob - $ rain_base ) /100) * $rainslope/ 100 ) ; # Rain Correction Faktor mit Steilheit
2021-01-26 20:38:22 +00:00
my $ kw = AttrVal ( $ name , 'Wh/kWh' , 'Wh' ) ;
2021-03-20 21:26:12 +00:00
my $ hc = ReadingsNum ( $ name , "pvCorrectionFactor_" . sprintf ( "%02d" , $ num ) , 1 ) ; # Korrekturfaktor für die Stunde des Tages
2021-03-19 18:06:34 +00:00
2021-01-26 20:38:22 +00:00
my $ pvsum = 0 ;
2021-03-20 21:26:12 +00:00
for my $ st ( @ strings ) { # für jeden String der Config ..
my $ peak = $ stch - > { "$st" } { peak } ; # String Peak (kWp)
my $ ta = $ stch - > { "$st" } { tilt } ; # Neigungswinkel Solarmodule
my $ moddir = $ stch - > { "$st" } { dir } ; # Ausrichtung der Solarmodule
2021-01-26 20:38:22 +00:00
2021-03-20 21:26:12 +00:00
my $ af = $ hff { $ ta } { $ moddir } / 100; # Flächenfaktor: http:/ /www.ing-büro-junge.de/ html / photovoltaik . html
2021-01-26 20:38:22 +00:00
$ hc = 1 if ( 1 * $ hc == 0 ) ;
2021-01-27 17:02:18 +00:00
2021-03-20 21:26:12 +00:00
my $ pv = sprintf "%.1f" , ( $ rad * $ af * $ kJtokWh * $ peak * $ pr * $ hc * $ ccf * $ rcf * 1000 ) ;
2021-01-26 20:38:22 +00:00
2021-03-20 21:26:12 +00:00
my $ lh = { # Log-Hash zur Ausgabe
2021-03-20 13:55:15 +00:00
"moduleDirection" = > $ moddir ,
"modulePeakString" = > $ peak ,
"moduleTiltAngle" = > $ ta ,
"Area factor" = > $ af ,
"Cloudcover" = > $ cloudcover ,
"CloudFactorSlope" = > $ cloudslope . " %" ,
"Cloudfactor" = > $ ccf ,
"Rainprob" = > $ rainprob ,
"Rainfactor" = > $ rcf ,
2021-03-20 14:14:18 +00:00
"RainFactorSlope" = > $ rainslope . " %" ,
2021-03-20 13:55:15 +00:00
"pvCorrectionFactor" = > $ hc ,
"Radiation" = > $ rad ,
"Factor kJ to kWh" = > $ kJtokWh ,
"PV generation" = > $ pv . " Wh"
2021-01-26 20:38:22 +00:00
} ;
my $ sq ;
for my $ idx ( sort keys % { $ lh } ) {
$ sq . = $ idx . " => " . $ lh - > { $ idx } . "\n" ;
}
2021-03-20 13:55:15 +00:00
Log3 ( $ name , 4 , "$name - PV forecast calc for $reld Hour " . sprintf ( "%02d" , $ chour + 1 ) . " string: $st ->\n$sq" ) ;
2021-01-26 20:38:22 +00:00
$ pvsum += $ pv ;
}
2021-01-21 22:07:18 +00:00
2020-12-31 09:09:49 +00:00
if ( $ kw eq "Wh" ) {
2021-01-26 20:38:22 +00:00
$ pvsum = int $ pvsum ;
2021-01-24 11:08:40 +00:00
}
2020-12-13 17:29:15 +00:00
2021-03-20 13:55:15 +00:00
Log3 ( $ name , 4 , "$name - PV forecast calc for $reld Hour " . sprintf ( "%02d" , $ chour + 1 ) . " summary: $pvsum" ) ;
2021-01-26 20:38:22 +00:00
return $ pvsum ;
2020-12-13 17:29:15 +00:00
}
2020-12-13 21:48:45 +00:00
################################################################
# Abweichung PVreal / PVforecast berechnen
2021-01-22 09:51:04 +00:00
# bei eingeschalteter automat. Korrektur
2020-12-13 21:48:45 +00:00
################################################################
sub calcVariance {
2020-12-27 18:51:53 +00:00
my $ paref = shift ;
2020-12-17 15:52:48 +00:00
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
2020-12-27 18:51:53 +00:00
my $ chour = $ paref - > { chour } ;
2021-01-19 09:10:20 +00:00
2020-12-17 15:52:48 +00:00
my $ dcauto = ReadingsVal ( $ name , "pvCorrectionFactor_Auto" , "off" ) ; # nur bei "on" automatische Varianzkalkulation
2020-12-16 09:33:20 +00:00
if ( $ dcauto =~ /^off/x ) {
2020-12-17 15:52:48 +00:00
Log3 ( $ name , 4 , "$name - automatic Variance calculation is switched off." ) ;
2020-12-15 13:41:10 +00:00
return ;
}
2020-12-27 18:51:53 +00:00
my $ tlim = "00" ; # Stunde 00 -> löschen aller Autocalc Statusreadings des Tages
if ( $ chour =~ /^($tlim)$/x ) {
deleteReadingspec ( $ hash , "pvCorrectionFactor_.*_autocalc" ) ;
}
my $ idts = ReadingsTimestamp ( $ name , "currentInverterDev" , "" ) ; # Definitionstimestamp des Inverterdevice
2020-12-13 21:48:45 +00:00
return if ( ! $ idts ) ;
2020-12-27 18:51:53 +00:00
$ idts = timestringToTimestamp ( $ hash , $ idts ) ;
2020-12-13 21:48:45 +00:00
2020-12-25 14:14:14 +00:00
my $ t = time ; # aktuelle Unix-Zeit
2020-12-13 21:48:45 +00:00
if ( $ t - $ idts < 86400 ) {
2020-12-15 13:41:10 +00:00
my $ rmh = sprintf "%.1f" , ( ( 86400 - ( $ t - $ idts ) ) / 3600 ) ;
2020-12-17 15:52:48 +00:00
Log3 ( $ name , 4 , "$name - Variance calculation in standby. It starts in $rmh hours." ) ;
readingsSingleUpdate ( $ hash , "pvCorrectionFactor_Auto" , "on (remains in standby for $rmh hours)" , 0 ) ;
2020-12-13 21:48:45 +00:00
return ;
2020-12-16 15:48:38 +00:00
}
else {
2020-12-17 15:52:48 +00:00
readingsSingleUpdate ( $ hash , "pvCorrectionFactor_Auto" , "on" , 0 ) ;
2020-12-27 20:04:17 +00:00
}
2021-01-26 20:38:22 +00:00
my $ maxvar = AttrVal ( $ name , "maxVariancePerDay" , $ defmaxvar ) ; # max. Korrekturvarianz
2020-12-13 21:48:45 +00:00
my @ da ;
for my $ h ( 1 .. 23 ) {
2020-12-17 13:35:52 +00:00
next if ( ! $ chour || $ h >= $ chour ) ;
2021-01-21 18:23:21 +00:00
my $ fcval = ReadingsNum ( $ name , "Today_Hour" . sprintf ( "%02d" , $ h ) . "_PVforecast" , 0 ) ;
2021-01-20 21:00:34 +00:00
next if ( ! $ fcval ) ;
2020-12-17 13:35:52 +00:00
2021-01-21 18:23:21 +00:00
my $ pvval = ReadingsNum ( $ name , "Today_Hour" . sprintf ( "%02d" , $ h ) . "_PVreal" , 0 ) ;
2020-12-16 19:15:48 +00:00
next if ( ! $ pvval ) ;
2020-12-27 18:51:53 +00:00
my $ cdone = ReadingsVal ( $ name , "pvCorrectionFactor_" . sprintf ( "%02d" , $ h ) . "_autocalc" , "" ) ;
if ( $ cdone eq "done" ) {
2021-01-27 17:02:18 +00:00
Log3 ( $ name , 5 , "$name - pvCorrectionFactor Hour: " . sprintf ( "%02d" , $ h ) . " already calculated" ) ;
2020-12-27 18:51:53 +00:00
next ;
}
2020-12-17 13:35:52 +00:00
2021-01-21 18:23:21 +00:00
my $ oldfac = ReadingsNum ( $ name , "pvCorrectionFactor_" . sprintf ( "%02d" , $ h ) , 1 ) ; # bisher definierter Korrekturfaktor
2020-12-16 19:15:48 +00:00
$ oldfac = 1 if ( 1 * $ oldfac == 0 ) ;
2021-01-20 21:00:34 +00:00
Log3 ( $ name , 5 , "$name - Hour: " . sprintf ( "%02d" , $ h ) . ", Today PVreal: $pvval, PVforecast: $fcval" ) ;
2020-12-15 13:41:10 +00:00
2021-01-21 18:23:21 +00:00
$ paref - > { hour } = $ h ;
my ( $ pvavg , $ fcavg ) = calcFromHistory ( $ paref ) ; # historische PV / Forecast Vergleichswerte ermitteln
2021-01-22 09:51:04 +00:00
$ pvval = $ pvavg ? ( $ pvval + $ pvavg ) / 2 : $ pvval ; # Ertrag aktuelle Stunde berücksichtigen
$ fcval = $ fcavg ? ( $ fcval + $ fcavg ) / 2 : $ fcval ; # Vorhersage aktuelle Stunde berücksichtigen
2021-01-21 18:23:21 +00:00
my $ factor = sprintf "%.2f" , ( $ pvval / $fcval); # Faktorberechnung: reale PV / Prognose
2020-12-27 20:04:17 +00:00
if ( abs ( $ factor - $ oldfac ) > $ maxvar ) {
$ factor = sprintf "%.2f" , ( $ factor > $ oldfac ? $ oldfac + $ maxvar : $ oldfac - $ maxvar ) ;
2021-01-22 09:51:04 +00:00
Log3 ( $ name , 3 , "$name - new limited Variance factor: $factor (old: $oldfac) for hour: $h" ) ;
2020-12-15 13:41:10 +00:00
}
else {
2021-01-22 09:51:04 +00:00
Log3 ( $ name , 3 , "$name - new Variance factor: $factor (old: $oldfac) for hour: $h calculated" ) if ( $ factor != $ oldfac ) ;
2020-12-15 13:41:10 +00:00
}
2020-12-17 08:44:29 +00:00
push @ da , "pvCorrectionFactor_" . sprintf ( "%02d" , $ h ) . ":" . $ factor . " (automatic)" ;
2020-12-27 18:51:53 +00:00
push @ da , "pvCorrectionFactor_" . sprintf ( "%02d" , $ h ) . "_autocalc:done" ;
2020-12-13 21:48:45 +00:00
}
2020-12-27 18:51:53 +00:00
createReadingsFromArray ( $ hash , \ @ da , 1 ) ;
2020-12-13 21:48:45 +00:00
return ;
}
2021-01-19 09:10:20 +00:00
################################################################
# Berechne Durchschnitte aus Werten der PV History
################################################################
sub calcFromHistory {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ t = $ paref - > { t } ; # aktuelle Unix-Zeit
2021-01-21 18:23:21 +00:00
my $ hour = $ paref - > { hour } ; # Stunde für die der Durchschnitt bestimmt werden soll
2021-01-19 09:10:20 +00:00
2021-01-22 09:51:04 +00:00
$ hour = sprintf ( "%02d" , $ hour ) ;
2021-01-20 21:00:34 +00:00
my $ name = $ hash - > { NAME } ;
my $ type = $ hash - > { TYPE } ;
my $ day = strftime "%d" , localtime ( $ t ) ; # aktueller Tag
my $ pvhh = $ data { $ type } { $ name } { pvhist } ;
2021-01-19 09:10:20 +00:00
2021-01-20 21:00:34 +00:00
my $ calcd = AttrVal ( $ name , "numHistDays" , $ calcmaxd ) ;
2021-01-19 09:10:20 +00:00
my @ k = sort { $ a <=> $ b } keys % { $ pvhh } ;
my $ ile = $# k ; # Index letztes Arrayelement
my ( $ idx ) = grep { $ k [ $ _ ] eq "$day" } ( 0 .. @ k - 1 ) ; # Index des aktuellen Tages
2021-01-20 08:49:39 +00:00
if ( defined $ idx ) {
my $ ei = $ idx - 1 ;
$ ei = $ ei < 0 ? $ ile : $ ei ;
my @ efa ;
2021-01-20 21:00:34 +00:00
for my $ e ( 0 .. $ calcd ) {
last if ( $ e == $ calcd || $ k [ $ ei ] == $ day ) ;
unshift @ efa , $ k [ $ ei ] ;
2021-01-20 08:49:39 +00:00
$ ei - - ;
}
2021-03-14 07:47:14 +00:00
my $ anzavg = scalar ( @ efa ) ;
if ( $ anzavg ) {
Log3 ( $ name , 4 , "$name - PV History -> Day $day has index $idx. Days ($calcd) for calc: " . join " " , @ efa ) ;
}
else { # vermeide Fehler: Illegal division by zero
Log3 ( $ name , 4 , "$name - PV History -> Day $day has index $idx. Use only current day for average calc" ) ;
return ;
}
2021-01-20 21:00:34 +00:00
my ( $ pvrl , $ pvfc ) = ( 0 , 0 ) ;
2021-01-22 09:51:04 +00:00
2021-01-20 21:00:34 +00:00
for my $ dayfa ( @ efa ) {
2021-01-22 09:51:04 +00:00
$ pvrl += $ pvhh - > { $ dayfa } { $ hour } { pvrl } // 0 ;
$ pvfc += $ pvhh - > { $ dayfa } { $ hour } { pvfc } // 0 ;
2021-01-20 21:00:34 +00:00
}
2021-01-22 10:06:56 +00:00
my $ pvavg = sprintf "%.2f" , $ pvrl / $ anzavg ;
my $ fcavg = sprintf "%.2f" , $ pvfc / $ anzavg ;
2021-01-20 21:00:34 +00:00
2021-01-21 18:23:21 +00:00
Log3 ( $ name , 4 , "$name - PV History -> average hour ($hour) -> real: $pvavg, forecast: $fcavg" ) ;
2021-01-20 21:00:34 +00:00
return ( $ pvavg , $ fcavg ) ;
2021-01-20 08:49:39 +00:00
}
2021-01-19 09:10:20 +00:00
return ;
}
2021-01-17 19:13:02 +00:00
################################################################
# PV und PV Forecast in History-Hash speichern zur
# Berechnung des Korrekturfaktors über mehrere Tage
################################################################
sub setPVhistory {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
2021-03-13 17:49:45 +00:00
my $ t = $ paref - > { t } ; # aktuelle Unix-Zeit
2021-03-13 17:25:42 +00:00
my $ nhour = $ paref - > { nhour } ;
2021-03-16 14:53:54 +00:00
my $ day = $ paref - > { day } ;
2021-03-17 20:06:19 +00:00
my $ histname = $ paref - > { histname } // qq{ } ;
my $ ethishour = $ paref - > { ethishour } // 0 ;
my $ calcpv = $ paref - > { calcpv } // 0 ;
my $ gthishour = $ paref - > { gctotthishour } // 0 ;
2021-01-17 19:13:02 +00:00
2021-03-16 14:53:54 +00:00
my $ type = $ hash - > { TYPE } ;
2021-03-14 06:29:17 +00:00
my $ val = q{ } ;
2021-01-22 20:16:33 +00:00
2021-03-14 06:29:17 +00:00
if ( $ histname eq "pvrl" ) { # realer Energieertrag
$ val = $ ethishour ;
2021-03-14 15:46:03 +00:00
$ data { $ type } { $ name } { pvhist } { $ day } { $ nhour } { pvrl } = $ ethishour ;
my $ pvrlsum = 0 ;
for my $ k ( keys % { $ data { $ type } { $ name } { pvhist } { $ day } } ) {
next if ( $ k eq "99" ) ;
$ pvrlsum += $ data { $ type } { $ name } { pvhist } { $ day } { $ k } { pvrl } // 0 ;
}
$ data { $ type } { $ name } { pvhist } { $ day } { 99 } { pvrl } = $ pvrlsum ;
2021-03-14 06:29:17 +00:00
}
if ( $ histname eq "pvfc" ) { # prognostizierter Energieertrag
$ val = $ calcpv ;
2021-03-14 15:46:03 +00:00
$ data { $ type } { $ name } { pvhist } { $ day } { $ nhour } { pvfc } = $ calcpv ;
my $ pvfcsum = 0 ;
for my $ k ( keys % { $ data { $ type } { $ name } { pvhist } { $ day } } ) {
next if ( $ k eq "99" ) ;
$ pvfcsum += $ data { $ type } { $ name } { pvhist } { $ day } { $ k } { pvfc } // 0 ;
}
$ data { $ type } { $ name } { pvhist } { $ day } { 99 } { pvfc } = $ pvfcsum ;
2021-03-14 06:29:17 +00:00
}
2021-03-17 20:06:19 +00:00
if ( $ histname eq "cons" ) { # bezogene Energie
$ val = $ gthishour ;
2021-03-18 10:31:34 +00:00
$ data { $ type } { $ name } { pvhist } { $ day } { $ nhour } { gcons } = $ gthishour ;
2021-03-17 20:06:19 +00:00
my $ gcsum = 0 ;
for my $ k ( keys % { $ data { $ type } { $ name } { pvhist } { $ day } } ) {
next if ( $ k eq "99" ) ;
2021-03-18 10:31:34 +00:00
$ gcsum += $ data { $ type } { $ name } { pvhist } { $ day } { $ k } { gcons } // 0 ;
2021-03-17 20:06:19 +00:00
}
2021-03-18 10:31:34 +00:00
$ data { $ type } { $ name } { pvhist } { $ day } { 99 } { gcons } = $ gcsum ;
2021-03-17 20:06:19 +00:00
}
2021-03-14 06:29:17 +00:00
Log3 ( $ name , 5 , "$name - set PV History hour: $nhour, hash: $histname, val: $val" ) ;
2021-01-17 19:13:02 +00:00
return ;
}
################################################################
2021-03-17 20:06:19 +00:00
# liefert aktuelle Einträge des in $htol
# angegebenen internen Hash
2021-01-17 19:13:02 +00:00
################################################################
2021-03-17 20:06:19 +00:00
sub listDataPool {
2021-01-17 19:13:02 +00:00
my $ hash = shift ;
2021-03-17 20:06:19 +00:00
my $ htol = shift ;
2021-01-17 19:13:02 +00:00
my $ name = $ hash - > { NAME } ;
my $ type = $ hash - > { TYPE } ;
2021-03-17 20:06:19 +00:00
my ( $ sq , $ h ) ;
2021-01-22 20:57:14 +00:00
2021-01-17 19:13:02 +00:00
my $ sub = sub {
my $ day = shift ;
my $ ret ;
2021-03-17 20:06:19 +00:00
for my $ key ( sort { $ a <=> $ b } keys % { $ h - > { $ day } } ) {
2021-03-18 10:31:34 +00:00
my $ pvrl = $ h - > { $ day } { $ key } { pvrl } // 0 ;
my $ pvfc = $ h - > { $ day } { $ key } { pvfc } // 0 ;
my $ cons = $ h - > { $ day } { $ key } { gcons } // 0 ;
2021-01-19 12:12:28 +00:00
$ ret . = "\n " if ( $ ret ) ;
2021-03-18 14:16:14 +00:00
$ ret . = $ key . " => pvreal: $pvrl, pvforecast: $pvfc, gridcon: $cons" ;
2021-01-17 19:13:02 +00:00
}
return $ ret ;
} ;
2021-03-17 20:06:19 +00:00
if ( $ htol eq "pvhist" ) {
$ h = $ data { $ type } { $ name } { pvhist } ;
if ( ! keys % { $ h } ) {
return qq{ PV cache is empty. } ;
}
for my $ idx ( reverse sort { $ a <=> $ b } keys % { $ h } ) {
$ sq . = $ idx . " => " . $ sub - > ( $ idx ) . "\n" ;
}
2021-01-17 19:13:02 +00:00
}
2021-03-17 20:06:19 +00:00
if ( $ htol eq "weather" ) {
2021-03-20 09:05:26 +00:00
$ h = $ data { $ type } { $ name } { weather } ;
2021-03-17 20:06:19 +00:00
if ( ! keys % { $ h } ) {
return qq{ Weather cache is empty. } ;
}
for my $ idx ( sort { $ a <=> $ b } keys % { $ h } ) {
2021-03-20 09:05:26 +00:00
$ sq . = $ idx . " => id: " . $ data { $ type } { $ name } { weather } { $ idx } { id } . "\n" ;
$ sq . = " => txt: " . $ data { $ type } { $ name } { weather } { $ idx } { txt } . "\n" ;
$ sq . = " => cloudcover: " . $ data { $ type } { $ name } { weather } { $ idx } { cloudcover } . "\n" ;
$ sq . = " => rainprob: " . $ data { $ type } { $ name } { weather } { $ idx } { rainprob } . "\n" ;
2021-03-17 20:06:19 +00:00
}
}
if ( $ htol eq "pvreal" ) {
$ h = $ data { $ type } { $ name } { pvreal } ;
if ( ! keys % { $ h } ) {
return qq{ PV real cache is empty. } ;
}
for my $ idx ( sort { $ a <=> $ b } keys % { $ h } ) {
$ sq . = $ idx . " => " . $ data { $ type } { $ name } { pvreal } { $ idx } . "\n" ;
}
2021-01-17 19:13:02 +00:00
}
2021-03-18 17:25:37 +00:00
if ( $ htol eq "pvfc" ) {
$ h = $ data { $ type } { $ name } { pvfc } ;
if ( ! keys % { $ h } ) {
return qq{ PV forecast cache is empty. } ;
}
for my $ idx ( sort { $ a <=> $ b } keys % { $ h } ) {
$ sq . = $ idx . " => " . $ data { $ type } { $ name } { pvfc } { $ idx } . "\n" ;
}
}
2021-03-20 09:05:26 +00:00
if ( $ htol eq "nexthours" ) {
$ h = $ data { $ type } { $ name } { nexthours } ;
if ( ! keys % { $ h } ) {
return qq{ NextHours cache is empty. } ;
}
for my $ idx ( sort keys % { $ h } ) {
2021-03-20 11:36:59 +00:00
my $ nhts = $ data { $ type } { $ name } { nexthours } { $ idx } { starttime } ;
2021-03-21 11:50:35 +00:00
my $ nhfc = $ data { $ type } { $ name } { nexthours } { $ idx } { pvforecast } ;
2021-03-20 21:26:12 +00:00
my $ wid = $ data { $ type } { $ name } { nexthours } { $ idx } { weatherid } ;
my $ neff = $ data { $ type } { $ name } { nexthours } { $ idx } { cloudcover } ;
my $ r101 = $ data { $ type } { $ name } { nexthours } { $ idx } { rainprob } ;
2021-03-20 09:05:26 +00:00
$ sq . = "\n" if ( $ sq ) ;
2021-03-20 21:26:12 +00:00
$ sq . = $ idx . " => starttime: $nhts, pvforecast: $nhfc, weatherid: $wid, cloudcover: $neff, rainprob: $r101" ;
2021-03-20 09:05:26 +00:00
}
}
2021-01-17 19:13:02 +00:00
return $ sq ;
}
2021-01-26 20:38:22 +00:00
################################################################
# liefert aktuelle Stringkonfiguration
# inkl. Vollständigkeitscheck
################################################################
sub checkStringConfig {
my $ hash = shift ;
my $ name = $ hash - > { NAME } ;
my $ type = $ hash - > { TYPE } ;
my $ stch = $ data { $ type } { $ name } { strings } ;
my $ sub = sub {
my $ string = shift ;
my $ ret ;
for my $ key ( sort keys % { $ stch - > { "$string" } } ) {
$ ret . = ", " if ( $ ret ) ;
$ ret . = $ key . ": " . $ stch - > { "$string" } { $ key } ;
}
return $ ret ;
} ;
if ( ! keys % { $ stch } ) {
return qq{ String configuration is empty. } ;
}
my $ sc ;
my $ cf = 0 ;
for my $ sn ( sort keys % { $ stch } ) {
my $ sp = $ sn . " => " . $ sub - > ( $ sn ) . "\n" ;
2021-01-27 17:02:18 +00:00
$ cf = 1 if ( $ sp !~ /dir.*?peak.*?tilt/x ) ; # Test Vollständigkeit: z.B. Süddach => dir: S, peak: 5.13, tilt: 45
2021-01-26 20:38:22 +00:00
$ sc . = $ sp ;
}
if ( $ cf ) {
2021-01-27 17:02:18 +00:00
$ sc . = "\n\nOh no 🙁, your string configuration is inconsistent.\nPlease check the settings of modulePeakString, moduleDirection, moduleTiltAngle !" ;
2021-01-26 20:38:22 +00:00
}
else {
$ sc . = "\n\nCongratulations 😊, your string configuration checked without found errors !" ;
}
return $ sc ;
}
2020-12-13 17:29:15 +00:00
################################################################
# Zusammenfassungen erstellen
################################################################
2021-01-24 08:35:16 +00:00
sub collectSummaries {
2020-12-13 17:29:15 +00:00
my $ hash = shift ;
my $ chour = shift ; # aktuelle Stunde
my $ daref = shift ;
my $ name = $ hash - > { NAME } ;
2021-03-20 21:26:12 +00:00
my $ type = $ hash - > { TYPE } ;
2020-12-13 17:29:15 +00:00
my $ next4HoursSum = { "PV" = > 0 , "Consumption" = > 0 , "Total" = > 0 , "ConsumpRcmd" = > 0 } ;
my $ restOfDaySum = { "PV" = > 0 , "Consumption" = > 0 , "Total" = > 0 , "ConsumpRcmd" = > 0 } ;
my $ tomorrowSum = { "PV" = > 0 , "Consumption" = > 0 , "Total" = > 0 , "ConsumpRcmd" = > 0 } ;
2021-01-24 08:35:16 +00:00
my $ todaySum = { "PV" = > 0 , "Consumption" = > 0 , "Total" = > 0 , "ConsumpRcmd" = > 0 } ;
2020-12-13 17:29:15 +00:00
my $ rdh = 24 - $ chour - 1 ; # verbleibende Anzahl Stunden am Tag beginnend mit 00 (abzüglich aktuelle Stunde)
2021-03-20 21:26:12 +00:00
my $ thforecast = $ data { $ type } { $ name } { nexthours } { NextHour00 } { pvforecast } ;
2020-12-13 17:29:15 +00:00
$ next4HoursSum - > { PV } = $ thforecast ;
$ restOfDaySum - > { PV } = $ thforecast ;
2020-12-15 21:27:21 +00:00
for my $ h ( 1 .. 47 ) {
2021-03-21 11:50:35 +00:00
next if ( ! $ data { $ type } { $ name } { nexthours } { "NextHour" . sprintf "%02d" , $ h } ) ;
2021-03-21 12:05:15 +00:00
my $ pvfc = $ data { $ type } { $ name } { nexthours } { "NextHour" . sprintf "%02d" , $ h } { pvforecast } // 0 ;
2021-03-21 11:50:35 +00:00
$ next4HoursSum - > { PV } += $ pvfc if ( $ h <= 3 ) ;
$ restOfDaySum - > { PV } += $ pvfc if ( $ h <= $ rdh ) ;
$ tomorrowSum - > { PV } += $ pvfc if ( $ h > $ rdh ) ;
$ todaySum - > { PV } += $ pvfc if ( $ h <= 23 ) ;
2020-12-13 17:29:15 +00:00
}
2021-03-18 22:30:51 +00:00
push @$ daref , "Next04Hours_PV:" . ( int $ next4HoursSum - > { PV } ) . " Wh" ;
push @$ daref , "RestOfDay_PV:" . ( int $ restOfDaySum - > { PV } ) . " Wh" ;
push @$ daref , "Tomorrow_PVforecast:" . ( int $ tomorrowSum - > { PV } ) . " Wh" ;
push @$ daref , "Today_PVforecast:" . ( int $ todaySum - > { PV } ) . " Wh" ;
2020-12-13 17:29:15 +00:00
2020-12-27 18:51:53 +00:00
createReadingsFromArray ( $ hash , $ daref , 1 ) ;
2020-12-13 17:29:15 +00:00
return ;
}
################################################################
2020-12-27 18:51:53 +00:00
# einen Zeitstring YYYY-MM-TT hh:mm:ss in einen Unix
# Timestamp umwandeln
################################################################
sub timestringToTimestamp {
my $ hash = shift ;
my $ tstring = shift ;
my $ name = $ hash - > { NAME } ;
my ( $ y , $ mo , $ d , $ h , $ m , $ s ) = $ tstring =~ /([0-9]{4})-([0-9]{2})-([0-9]{2})\s([0-9]{2}):([0-9]{2}):([0-9]{2})/xs ;
return if ( ! $ mo || ! $ y ) ;
my $ timestamp = fhemTimeLocal ( $ s , $ m , $ h , $ d , $ mo - 1 , $ y - 1900 ) ;
return $ timestamp ;
}
################################################################
# Readings aus Array erstellen
# $daref: Referenz zum Array der zu erstellenden Readings
# muß Paare <Readingname>:<Wert> enthalten
# $doevt: 1-Events erstellen, 0-keine Events erstellen
2021-03-17 20:06:19 +00:00
#
# readingsBulkUpdate($hash,$reading,$value,$changed,$timestamp)
#
2020-12-13 17:29:15 +00:00
################################################################
2020-12-27 18:51:53 +00:00
sub createReadingsFromArray {
2020-12-13 17:29:15 +00:00
my $ hash = shift ;
my $ daref = shift ;
2020-12-27 18:51:53 +00:00
my $ doevt = shift // 0 ;
2020-12-13 17:29:15 +00:00
readingsBeginUpdate ( $ hash ) ;
for my $ elem ( @$ daref ) {
my ( $ rn , $ rval ) = split ":" , $ elem , 2 ;
readingsBulkUpdate ( $ hash , $ rn , $ rval ) ;
}
2020-12-27 18:51:53 +00:00
readingsEndUpdate ( $ hash , $ doevt ) ;
2020-12-13 17:29:15 +00:00
return ;
}
2020-12-16 09:15:37 +00:00
################################################################
2021-03-14 07:47:14 +00:00
# alle Readings eines Devices oder nur Reading-Regex
2020-12-16 09:15:37 +00:00
# löschen
################################################################
sub deleteReadingspec {
my $ hash = shift ;
my $ spec = shift // ".*" ;
my $ readingspec = '^' . $ spec . '$' ;
for my $ reading ( grep { /$readingspec/ } keys % { $ hash - > { READINGS } } ) {
readingsDelete ( $ hash , $ reading ) ;
}
return ;
}
2020-12-13 17:29:15 +00:00
######################################################################################
# NOTIFYDEV erstellen
######################################################################################
sub createNotifyDev {
my $ hash = shift ;
my $ name = $ hash - > { NAME } ;
RemoveInternalTimer ( $ hash , "FHEM::SolarForecast::createNotifyDev" ) ;
if ( $ init_done == 1 ) {
my @ nd ;
2020-12-15 13:41:10 +00:00
my ( $ a , $ h ) ;
2020-12-27 18:51:53 +00:00
my $ fcdev = ReadingsVal ( $ name , "currentForecastDev" , "" ) ; # Forecast Device
( $ a , $ h ) = parseParams ( $ fcdev ) ;
$ fcdev = $ a - > [ 0 ] // "" ;
2020-12-13 17:29:15 +00:00
2020-12-27 18:51:53 +00:00
my $ indev = ReadingsVal ( $ name , "currentInverterDev" , "" ) ; # Inverter Device
2020-12-15 13:41:10 +00:00
( $ a , $ h ) = parseParams ( $ indev ) ;
$ indev = $ a - > [ 0 ] // "" ;
2020-12-27 18:51:53 +00:00
my $ medev = ReadingsVal ( $ name , "currentMeterDev" , "" ) ; # Meter Device
2020-12-15 13:41:10 +00:00
( $ a , $ h ) = parseParams ( $ medev ) ;
$ medev = $ a - > [ 0 ] // "" ;
2020-12-13 17:29:15 +00:00
push @ nd , $ fcdev ;
push @ nd , $ indev ;
push @ nd , $ medev ;
2020-12-27 18:51:53 +00:00
if ( @ nd ) {
$ hash - > { NOTIFYDEV } = join "," , @ nd ;
readingsSingleUpdate ( $ hash , ".associatedWith" , join ( " " , @ nd ) , 0 ) ;
}
2020-12-13 17:29:15 +00:00
}
else {
InternalTimer ( gettimeofday ( ) + 3 , "FHEM::SolarForecast::createNotifyDev" , $ hash , 0 ) ;
}
return ;
}
1 ;
= pod
= item summary Visualization of solar predictions for PV systems
= item summary_DE Visualisierung von solaren Vorhersagen für PV Anlagen
= begin html
= end html
= begin html_DE
< a name = "SolarForecast" > </a>
<h3> SolarForecast </h3>
<br>
2020-12-27 18:51:53 +00:00
2021-01-19 09:10:20 +00:00
Das Modul SolarForecast erstellt auf Grundlage der Werte aus generischen Quellendevices eine
2020-12-15 13:41:10 +00:00
Vorhersage für den solaren Ertrag und weitere Informationen als Grundlage für abhängige Steuerungen . <br>
2020-12-13 17:29:15 +00:00
Die Solargrafik kann ebenfalls in FHEM Tablet UI mit dem
2021-01-26 20:38:22 +00:00
< a href = "https://wiki.fhem.de/wiki/FTUI_Widget_SMAPortalSPG" > "SolarForecast Widget" </a> integriert werden . <br> <br>
2020-12-13 17:29:15 +00:00
Die solare Vorhersage basiert auf der durch den Deutschen Wetterdienst ( DWD ) prognostizierten Globalstrahlung am
Anlagenstandort . Im zugeordneten DWD_OpenData Device ist die passende Wetterstation mit dem Attribut "forecastStation"
2021-01-19 09:10:20 +00:00
festzulegen um eine Prognose für diesen Standort zu erhalten . <br>
2020-12-13 17:29:15 +00:00
Abhängig von der physikalischen Anlagengestaltung ( Ausrichtung , Winkel , Aufteilung in mehrere Strings , u . a . ) wird die
2021-01-26 20:38:22 +00:00
verfügbare Globalstrahlung ganz spezifisch in elektrische Energie umgewandelt . <br>
Ein SolarForecast Device unterstützt einen Wechselrichter mit beliebig vielen angeschlossenen Strings . Weiteren Wechselrichtern
werden weitere SolarForecast Devices zugeordnet .
2020-12-13 17:29:15 +00:00
<ul>
< a name = "SolarForecastdefine" > </a>
<b> Define </b>
<br> <br>
<ul>
Ein SolarForecast Device wird einfach erstellt mit: <br> <br>
<ul>
<b> define & lt ; name & gt ; SolarForecast </b>
</ul>
<br>
Nach der Definition des Devices ist zwingend ein Vorhersage - Device des Typs DWD_OpenData zuzuordnen sowie weitere
2021-01-19 09:10:20 +00:00
anlagenspezifische Angaben mit dem entsprechenden set - Kommando vorzunehmen . <br>
Mit nachfolgenden set - Kommandos werden die Quellendevices und Quellenreadings für maßgebliche Informationen
hinterlegt: <br> <br>
<ul>
<table>
<colgroup> < col width = 35 % > < col width = 65 % > < / colgroup >
<tr> <td> <b> currentForecastDev </b> </td> <td> Device welches Strahlungsdaten liefert </td> </tr>
<tr> <td> <b> currentInverterDev </b> </td> <td> Device welches PV Leistungsdaten liefert </td> </tr>
<tr> <td> <b> currentMeterDev </b> </td> <td> Device welches aktuelle Netzbezugsdaten liefert </td> </tr>
</table>
</ul>
<br>
Um eine Anpassung an die persönliche Anlage zu ermöglichen , können Korrekturfaktoren manuell
( set & lt ; name & gt ; pvCorrectionFactor_XX ) bzw . automatisiert ( set & lt ; name & gt ; pvCorrectionFactor_Auto ) eingefügt
werden .
2020-12-13 17:29:15 +00:00
<br> <br>
</ul>
< a name = "SolarForecastset" > </a>
<b> Set </b>
<ul>
<ul>
2020-12-27 18:51:53 +00:00
< a name = "currentForecastDev" > </a>
<li> <b> currentForecastDev </b> <br>
2020-12-13 17:29:15 +00:00
Legt das Device ( Typ DWD_OpenData ) fest , welches die Daten der solaren Vorhersage liefert . Ist noch kein Device dieses Typs
vorhanden , muß es manuell definiert werden ( siehe < a href = "http://fhem.de/commandref.html#DWD_OpenData" > DWD_OpenData Commandref </a> ) . <br>
Im ausgewählten DWD_OpenData Device müssen mindestens diese Attribute gesetzt sein: <br> <br>
<ul>
<table>
2021-03-14 18:20:19 +00:00
<colgroup> < col width = 25 % > < col width = 75 % > < / colgroup >
2020-12-27 18:51:53 +00:00
<tr> <td> <b> forecastDays </b> </td> <td> 1 </td> </tr>
2021-01-23 08:01:53 +00:00
<tr> <td> <b> forecastProperties </b> </td> <td> Rad1h , TTT , Neff , R101 , ww , SunUp , SunRise , SunSet </td> </tr>
2020-12-27 18:51:53 +00:00
<tr> <td> <b> forecastResolution </b> </td> <td> 1 </td> </tr>
<tr> <td> <b> forecastStation </b> </td> <td> & lt ; Stationscode der ausgewerteten DWD Station & gt ; </td> </tr>
<tr> <td> </td> <td> <b> Hinweis: </b> Die ausgewählte forecastStation muß Strahlungswerte ( Rad1h Readings ) liefern . </td> </tr>
2020-12-13 17:29:15 +00:00
</table>
2020-12-27 18:51:53 +00:00
</ul>
2020-12-13 17:29:15 +00:00
</li>
</ul>
<br>
<ul>
2020-12-27 18:51:53 +00:00
< a name = "currentInverterDev" > </a>
2021-03-17 20:06:19 +00:00
<li> <b> currentInverterDev & lt ; Inverter Device Name & gt ; pv = & lt ; Reading aktuelle PV - Leistung & gt ; : & lt ; Einheit & gt ; etotal = & lt ; Reading Summe Energieerzeugung & gt ; : & lt ; Einheit & gt ; </b> <br>
2020-12-15 13:41:10 +00:00
Legt ein beliebiges Device zur Lieferung der aktuellen PV Erzeugungswerte fest .
2021-01-23 12:15:30 +00:00
<br>
2021-03-14 18:20:19 +00:00
<ul>
<table>
<colgroup> < col width = 10 % > < col width = 90 % > < / colgroup >
<tr> <td> <b> pv </b> </td> <td> Reading mit aktueller PV - Leistung </td> </tr>
2021-03-16 14:53:54 +00:00
<tr> <td> <b> etotal </b> </td> <td> ein stetig aufsteigender Zähler der gesamten erzeugten Energie </td> </tr>
2021-03-14 18:20:19 +00:00
<tr> <td> <b> Einheit </b> </td> <td> die jeweilige Einheit ( W , kW , Wh , kWh ) </td> </tr>
</table>
</ul>
<br>
2020-12-13 17:29:15 +00:00
2020-12-15 13:41:10 +00:00
<ul>
<b> Beispiel: </b> <br>
2021-03-16 14:53:54 +00:00
set & lt ; name & gt ; currentInverterDev STP5000 pv = total_pac:kW etotal = etotal:kWh <br>
2021-01-23 12:15:30 +00:00
# Device STP5000 liefert PV-Werte. Die aktuell erzeugte Leistung im Reading "total_pac" (kW) und die tägliche Erzeugung im
2021-03-16 14:53:54 +00:00
Reading "etotal" ( kWh )
2020-12-15 13:41:10 +00:00
</ul>
2020-12-13 17:29:15 +00:00
</li>
</ul>
<br>
<ul>
2020-12-27 18:51:53 +00:00
< a name = "currentMeterDev" > </a>
2021-03-17 20:06:19 +00:00
<li> <b> currentMeterDev & lt ; Meter Device Name & gt ; gcon = & lt ; Reading aktueller Netzbezug & gt ; : & lt ; Einheit & gt ; contotal = & lt ; Reading Summe Netzbezug & gt ; : & lt ; Einheit & gt ; </b> <br>
Legt ein beliebiges Device zur Messung des Energiebezugs fest .
2021-03-14 18:20:19 +00:00
<br>
<ul>
<table>
<colgroup> < col width = 15 % > < col width = 85 % > < / colgroup >
2021-03-17 20:06:19 +00:00
<tr> <td> <b> gcon </b> </td> <td> Reading welches die aktuell aus dem Netz bezogene Leistung liefert </td> </tr>
<tr> <td> <b> contotal </b> </td> <td> Reading welches die Summe der aus dem Netz bezogenen Energie liefert </td> </tr>
<tr> <td> <b> Einheit </b> </td> <td> die jeweilige Einheit ( W , kW , Wh , kWh ) </td> </tr>
2021-03-14 18:20:19 +00:00
</table>
</ul>
<br>
2020-12-15 13:41:10 +00:00
<ul>
<b> Beispiel: </b> <br>
2021-03-17 20:06:19 +00:00
set & lt ; name & gt ; currentMeterDev SMA_Energymeter gcon = Bezug_Wirkleistung:W contotal = Bezug_Wirkleistung_Zaehler:kWh <br>
# Device SMA_Energymeter liefert den aktuellen Netzbezug im Reading "Bezug_Wirkleistung" (W) und den totalen Bezug im Reading "Bezug_Wirkleistung_Zaehler" (kWh)
2020-12-15 13:41:10 +00:00
</ul>
2020-12-13 17:29:15 +00:00
</li>
</ul>
<br>
2021-01-26 20:38:22 +00:00
<ul>
< a name = "inverterStrings" > </a>
<li> <b> inverterStrings & lt ; Stringname1 & gt ; [ , & lt ; Stringname2 & gt ; , & lt ; Stringname3 & gt ; , ... ] </b> <br>
Bezeichnungen der am Wechselrichter aktiven Strings . Diese Bezeichnungen werden als Schlüssel in den weiteren
Settings verwendet . <br> <br>
<ul>
<b> Beispiel: </b> <br>
set & lt ; name & gt ; inverterStrings Ostdach , Südgarage , S3 <br>
</ul>
</li>
</ul>
<br>
2020-12-13 17:29:15 +00:00
<ul>
2021-01-27 17:02:18 +00:00
< a name = "modulePeakString" > </a>
<li> <b> modulePeakString & lt ; Stringname1 & gt ; = & lt ; Peak & gt ; [ & lt ; Stringname2 & gt ; = & lt ; Peak & gt ; & lt ; Stringname3 & gt ; = & lt ; Peak & gt ; ... ] </b> <br>
Die Peakleistung des Strings "StringnameX" in kWp . Der Stringname ist ein Schlüsselwert des
2021-01-26 20:38:22 +00:00
Readings <b> inverterStrings </b> . <br> <br>
<ul>
<b> Beispiel: </b> <br>
2021-01-27 17:02:18 +00:00
set & lt ; name & gt ; modulePeakString Ostdach = 5.1 Südgarage = 2.0 S3 = 7.2 <br>
2021-01-26 20:38:22 +00:00
</ul>
2021-01-24 18:03:44 +00:00
</li>
</ul>
<br>
<ul>
< a name = "moduleDirection" > </a>
2021-01-26 20:38:22 +00:00
<li> <b> moduleDirection & lt ; Stringname1 & gt ; = & lt ; dir & gt ; [ & lt ; Stringname2 & gt ; = & lt ; dir & gt ; & lt ; Stringname3 & gt ; = & lt ; dir & gt ; ... ] </b> <br>
Ausrichtung & lt ; dir & gt ; der Solarmodule im String "StringnameX" . Der Stringname ist ein Schlüsselwert des
Readings <b> inverterStrings </b> . <br>
Die Richtungsangabe & lt ; dir & gt ; kann eine der folgenden Werte sein: <br> <br>
2021-01-24 18:03:44 +00:00
<ul>
<table>
<colgroup> < col width = 10 % > < col width = 90 % > < / colgroup >
<tr> <td> <b> N </b> </td> <td> Nordausrichtung </td> </tr>
<tr> <td> <b> NE </b> </td> <td> Nord - Ost Ausrichtung </td> </tr>
<tr> <td> <b> E </b> </td> <td> Ostausrichtung </td> </tr>
<tr> <td> <b> SE </b> </td> <td> Süd - Ost Ausrichtung </td> </tr>
<tr> <td> <b> S </b> </td> <td> Südausrichtung </td> </tr>
<tr> <td> <b> SW </b> </td> <td> Süd - West Ausrichtung </td> </tr>
<tr> <td> <b> W </b> </td> <td> Westausrichtung </td> </tr>
<tr> <td> <b> NW </b> </td> <td> Nord - West Ausrichtung </td> </tr>
</table>
2021-01-26 20:38:22 +00:00
</ul>
<br>
<ul>
<b> Beispiel: </b> <br>
set & lt ; name & gt ; moduleDirection Ostdach = E Südgarage = S S3 = NW <br>
2021-01-24 18:03:44 +00:00
</ul>
2020-12-13 17:29:15 +00:00
</li>
</ul>
<br>
2020-12-16 19:15:48 +00:00
<ul>
< a name = "moduleTiltAngle" > </a>
2021-01-26 20:38:22 +00:00
<li> <b> moduleTiltAngle & lt ; Stringname1 & gt ; = & lt ; Winkel & gt ; [ & lt ; Stringname2 & gt ; = & lt ; Winkel & gt ; & lt ; Stringname3 & gt ; = & lt ; Winkel & gt ; ... ] </b> <br>
Neigungswinkel der Solarmodule . Der Stringname ist ein Schlüsselwert des Readings <b> inverterStrings </b> . <br>
Mögliche Neigungswinkel sind: 0 , 10 , 20 , 30 , 40 , 45 , 50 , 60 , 70 , 80 , 90 ( 0 = waagerecht , 90 = senkrecht ) . <br> <br>
<ul>
<b> Beispiel: </b> <br>
set & lt ; name & gt ; moduleTiltAngle Ostdach = 40 Südgarage = 60 S3 = 30 <br>
</ul>
2020-12-16 19:15:48 +00:00
</li>
</ul>
<br>
2020-12-15 13:41:10 +00:00
<ul>
< a name = "pvCorrectionFactor_Auto" > </a>
<li> <b> pvCorrectionFactor_Auto & lt ; on | off & gt ; </b> <br>
Schaltet die automatische Vorhersagekorrektur ein / aus . <br>
2020-12-15 21:27:21 +00:00
Ist die Automatik eingeschaltet , wird nach einer Mindestlaufzeit von FHEM bzw . des Moduls von 24 Stunden für jede Stunde
ein Korrekturfaktor der Solarvorhersage berechnet und auf die Erwartung des kommenden Tages angewendet .
2020-12-15 13:41:10 +00:00
Dazu wird die tatsächliche Energierzeugung mit dem vorhergesagten Wert des aktuellen Tages und Stunde vergleichen und
daraus eine Korrektur abgeleitet . <br>
( default: off )
</li>
</ul>
<br>
2020-12-13 17:29:15 +00:00
<ul>
< a name = "pvCorrectionFactor_XX" > </a>
<li> <b> pvCorrectionFactor_XX & lt ; Zahl & gt ; </b> <br>
Manueller Korrekturfaktor für die Stunde XX des Tages zur Anpassung der Vorhersage an die individuelle Anlage . <br>
( default: 1.0 )
</li>
</ul>
<br>
2021-01-19 17:23:24 +00:00
<ul>
< a name = "reset" > </a>
<li> <b> reset </b> <br>
Löscht die aus der Drop - Down Liste gewählte Datenquelle . <br>
</li>
</ul>
<br>
2021-01-23 21:31:11 +00:00
<ul>
< a name = "writeHistory" > </a>
<li> <b> writeHistory </b> <br>
2021-01-24 08:35:16 +00:00
Die vom Device gesammelten historischen PV Daten werden in ein File geschrieben . Dieser Vorgang wird per default
regelmäßig im Hintergrund ausgeführt . Im Internal "HISTFILE" wird der Filename und der Zeitpunkt der letzten
Speicherung dokumentiert . <br>
2021-01-23 21:31:11 +00:00
</li>
</ul>
<br>
2020-12-13 17:29:15 +00:00
</ul>
<br>
< a name = "SolarForecastget" > </a>
<b> Get </b>
<ul>
<ul>
< a name = "html" > </a>
<li> <b> html </b> <br>
Die Solar Grafik wird als HTML - Code abgerufen und wiedergegeben .
</li>
</ul>
<br>
2020-12-15 13:54:31 +00:00
<ul>
< a name = "data" > </a>
<li> <b> data </b> <br>
Startet die Datensammlung zur Bestimmung der solaren Vorhersage und anderer Werte .
</li>
</ul>
<br>
2021-03-20 11:36:59 +00:00
<ul>
< a name = "nextHours" > </a>
<li> <b> nextHours </b> <br>
Listet die erwarteten Werte der nächsten Stunden auf .
</li>
</ul>
<br>
2021-01-17 19:13:02 +00:00
<ul>
< a name = "pvHistory" > </a>
<li> <b> pvHistory </b> <br>
2021-03-19 13:17:55 +00:00
Listet die historischen Werte der letzten Tage ( max . 31 ) sortiert nach dem Tagesdatum und Stunde .
Die Stundenangaben beziehen sich auf die Stunde des Tages , z . B . bezieht sich die Stunde 09 auf die Zeit von 08 - 09 Uhr .
2021-03-18 14:16:14 +00:00
Dabei sind <b> pvreal </b> der reale PV Ertrag , <b> pvforecast </b> der prognostizierte PV Ertrag und <b> gridcon </b>
2021-03-19 13:17:55 +00:00
der Netzbezug der jeweiligen Stunde .
2021-01-17 19:13:02 +00:00
</li>
</ul>
<br>
2021-03-18 22:12:08 +00:00
<ul>
< a name = "pvForecast" > </a>
<li> <b> pvForecast </b> <br>
2021-03-19 13:17:55 +00:00
Listet die im Ringspeicher vorhandenen PV Vorhersagewerte der kommenden 24 h auf .
Die Stundenangaben beziehen sich auf die Stunde des Tages , z . B . bezieht sich die Stunde 09 auf die Zeit von 08 - 09 Uhr .
2021-03-18 22:12:08 +00:00
</li>
</ul>
<br>
2021-03-17 20:06:19 +00:00
<ul>
< a name = "pvReal" > </a>
<li> <b> pvReal </b> <br>
2021-03-19 13:17:55 +00:00
Listet die im Ringspeicher vorhandenen PV Erzeugungswerte der letzten 24 h auf .
Die Stundenangaben beziehen sich auf die Stunde des Tages , z . B . bezieht sich die Stunde 09 auf die Zeit von 08 - 09 Uhr .
2021-03-17 20:06:19 +00:00
</li>
</ul>
<br>
<ul>
< a name = "weatherData" > </a>
<li> <b> weatherData </b> <br>
2021-03-19 13:17:55 +00:00
Listet die im Ringspeicher vorhandenen Wetterdaten der kommenden 24 h auf .
Die Stundenangaben beziehen sich auf die Stunde des Tages , z . B . bezieht sich die Stunde 09 auf die Zeit von 08 - 09 Uhr .
2021-03-17 20:06:19 +00:00
</li>
</ul>
<br>
2020-12-13 17:29:15 +00:00
</ul>
<br>
< a name = "SolarForecastattr" > </a>
<b> Attribute </b>
<br> <br>
<ul>
<ul>
< a name = "alias" > </a>
<li> <b> alias </b> <br>
2020-12-27 18:51:53 +00:00
In Verbindung mit "showLink" ein beliebiger Anzeigename .
2020-12-13 17:29:15 +00:00
</li>
<br>
< a name = "autoRefresh" > </a>
<li> <b> autoRefresh </b> <br>
Wenn gesetzt , werden aktive Browserseiten des FHEMWEB - Devices welches das SolarForecast - Device aufgerufen hat , nach der
eingestellten Zeit ( Sekunden ) neu geladen . Sollen statt dessen Browserseiten eines bestimmten FHEMWEB - Devices neu
geladen werden , kann dieses Device mit dem Attribut "autoRefreshFW" festgelegt werden .
</li>
<br>
< a name = "autoRefreshFW" > </a>
<li> <b> autoRefreshFW </b> <br>
Ist "autoRefresh" aktiviert , kann mit diesem Attribut das FHEMWEB - Device bestimmt werden dessen aktive Browserseiten
regelmäßig neu geladen werden sollen .
</li>
<br>
2021-03-14 11:51:38 +00:00
< a name = "beam1Color" > </a>
<li> <b> beam1Color </b> <br>
2020-12-13 17:29:15 +00:00
Farbauswahl der primären Balken .
</li>
<br>
2021-03-14 11:51:38 +00:00
< a name = "beam1Content" > </a>
<li> <b> beam1Content </b> <br>
Legt den darzustellenden Inhalt der primären Balken fest .
<ul>
<table>
<colgroup> < col width = 10 % > < col width = 90 % > < / colgroup >
<tr> <td> <b> forecast </b> </td> <td> Vorhersage der PV - Erzeugung ( default ) </td> </tr>
<tr> <td> <b> real </b> </td> <td> tatsächliche PV - Erzeugung </td> </tr>
<tr> <td> <b> consumption </b> </td> <td> Energie Bezug aus dem Netz </td> </tr>
</table>
</ul>
</li>
<br>
< a name = "beam2Color" > </a>
<li> <b> beam2Color </b> <br>
Farbauswahl der sekundären Balken . Die zweite Farbe ist nur sinnvoll für den Anzeigedevice - Typ "pvco" und "diff" .
2020-12-13 17:29:15 +00:00
</li>
<br>
2021-03-14 11:51:38 +00:00
< a name = "beam2Content" > </a>
<li> <b> beam2Content </b> <br>
Legt den darzustellenden Inhalt der sekundären Balken fest .
<ul>
<table>
<colgroup> < col width = 10 % > < col width = 90 % > < / colgroup >
<tr> <td> <b> forecast </b> </td> <td> Vorhersage der PV - Erzeugung ( default ) </td> </tr>
<tr> <td> <b> real </b> </td> <td> tatsächliche PV - Erzeugung </td> </tr>
<tr> <td> <b> consumption </b> </td> <td> Energie Bezug aus dem Netz </td> </tr>
</table>
</ul>
</li>
<br>
2020-12-13 17:29:15 +00:00
< a name = "beamHeight" > </a>
<li> <b> beamHeight & lt ; value & gt ; </b> <br>
Höhe der Balken in px und damit Bestimmung der gesammten Höhe .
In Verbindung mit "hourCount" lassen sich damit auch recht kleine Grafikausgaben erzeugen . <br>
( default: 200 )
</li>
<br>
< a name = "beamWidth" > </a>
<li> <b> beamWidth & lt ; value & gt ; </b> <br>
Breite der Balken in px . <br>
( default: 6 ( auto ) )
</li>
<br>
2021-03-14 11:51:38 +00:00
2021-03-20 13:55:15 +00:00
< a name = "cloudFactorSlope" > </a>
<li> <b> cloudFactorSlope </b> <br>
2021-03-20 14:14:18 +00:00
Prozentuale Berücksichtigung ( Steilheit ) der Bewölkung bei der solaren Vorhersage . <br>
2021-03-20 13:55:15 +00:00
Höhere Werte vermindern tendenziell den prognostizierten PV Ertrag , kleinere Werte erhöhen den prognostizierten PV
Ertrag tendenziell . <br>
( default: 45 )
2021-03-14 11:51:38 +00:00
</li>
2021-03-20 13:55:15 +00:00
<br>
2020-12-13 17:29:15 +00:00
< a name = "disable" > </a>
<li> <b> disable </b> <br>
Aktiviert / deaktiviert das Device .
</li>
<br>
< a name = "forcePageRefresh" > </a>
<li> <b> forcePageRefresh </b> <br>
Das Attribut wird durch das SMAPortal - Device ausgewertet . <br>
Wenn gesetzt , wird ein Reload aller Browserseiten mit aktiven FHEMWEB - Verbindungen nach dem Update des
Eltern - SMAPortal - Devices erzwungen .
</li>
<br>
< a name = "headerAlignment" > </a>
<li> <b> headerAlignment & lt ; center | left | right & gt ; </b> <br>
Ausrichtung der Kopfzeilen . <br>
( default: center )
</li>
<br>
2021-01-20 21:00:34 +00:00
< a name = "hourCount" > </a>
<li> <b> hourCount & lt ; 4 ... 24 & gt ; </b> <br>
Anzahl der Balken / Stunden . <br>
( default: 24 )
</li>
<br>
2020-12-13 17:29:15 +00:00
< a name = "headerDetail" > </a>
<li> <b> headerDetail & lt ; all | co | pv | pvco | statusLink & gt ; </b> <br>
Detailiierungsgrad der Kopfzeilen . <br>
( default: all )
<ul>
<table>
<colgroup> < col width = 15 % > < col width = 85 % > < / colgroup >
<tr> <td> <b> all </b> </td> <td> Anzeige Erzeugung ( PV ) , Verbrauch ( CO ) , Link zur Device Detailanzeige + Aktualisierungszeit ( default ) </td> </tr>
<tr> <td> <b> co </b> </td> <td> nur Verbrauch ( CO ) </td> </tr>
<tr> <td> <b> pv </b> </td> <td> nur Erzeugung ( PV ) </td> </tr>
<tr> <td> <b> pvco </b> </td> <td> Erzeugung ( PV ) und Verbrauch ( CO ) </td> </tr>
<tr> <td> <b> statusLink </b> </td> <td> Link zur Device Detailanzeige + Aktualisierungszeit </td> </tr>
</table>
</ul>
</li>
<br>
< a name = "hourStyle" > </a>
<li> <b> hourStyle </b> <br>
Format der Zeitangabe . <br> <br>
2020-12-15 13:41:10 +00:00
<ul>
2020-12-13 17:29:15 +00:00
<table>
2020-12-15 13:41:10 +00:00
<colgroup> < col width = 10 % > < col width = 90 % > < / colgroup >
<tr> <td> <b> nicht gesetzt </b> </td> <td> - nur Stundenangabe ohne Minuten ( default ) </td> </tr>
<tr> <td> <b> : 00 </b> </td> <td> - Stunden sowie Minuten zweistellig , z . B . 10 : 00 </td> </tr>
<tr> <td> <b> : 0 </b> </td> <td> - Stunden sowie Minuten einstellig , z . B . 8 : 0 </td> </tr>
2020-12-13 17:29:15 +00:00
</table>
2020-12-15 13:41:10 +00:00
</ul>
2020-12-13 17:29:15 +00:00
</li>
<br>
2020-12-15 13:41:10 +00:00
2021-01-20 21:00:34 +00:00
< a name = "htmlStart" > </a>
<li> <b> htmlStart & lt ; HTML - String & gt ; </b> <br>
Angabe eines beliebigen HTML - Strings der vor dem Grafik - Code ausgeführt wird .
</li>
<br>
< a name = "htmlEnd" > </a>
<li> <b> htmlEnd & lt ; HTML - String & gt ; </b> <br>
Angabe eines beliebigen HTML - Strings der nach dem Grafik - Code ausgeführt wird .
</li>
<br>
2020-12-15 13:41:10 +00:00
< a name = "interval" > </a>
<li> <b> interval & lt ; Sekunden & gt ; </b> <br>
Zeitintervall der Datensammlung . <br>
Ist interval explizit auf "0" gesetzt , erfolgt keine automatische Datensammlung und muss mit "get <name> data"
manuell erfolgen . <br>
( default: 70 )
</li> <br>
2021-03-14 11:51:38 +00:00
< a name = "layoutType" > </a>
<li> <b> layoutType & lt ; pv | co | pvco | diff & gt ; </b> <br>
Layout der integrierten Grafik . <br>
( default: pv )
<br> <br>
<ul>
<table>
<colgroup> < col width = 15 % > < col width = 85 % > < / colgroup >
<tr> <td> <b> pv </b> </td> <td> - Erzeugung </td> </tr>
<tr> <td> <b> co </b> </td> <td> - Verbrauch </td> </tr>
<tr> <td> <b> pvco </b> </td> <td> - Erzeugung und Verbrauch </td> </tr>
<tr> <td> <b> diff </b> </td> <td> - Differenz von Erzeugung und Verbrauch </td> </tr>
</table>
</ul>
</li>
<br>
2020-12-13 17:29:15 +00:00
< a name = "maxPV" > </a>
<li> <b> maxPV & lt ; 0 ... val & gt ; </b> <br>
Maximaler Ertrag in einer Stunde zur Berechnung der Balkenhöhe . <br>
( default: 0 - > dynamisch )
</li>
<br>
2020-12-27 20:04:17 +00:00
< a name = "maxVariancePerDay" > </a>
<li> <b> maxVariancePerDay & lt ; Zahl & gt ; </b> <br>
Maximale Änderungsgröße des PV Vorhersagefaktors ( Reading pvCorrectionFactor_XX ) pro Tag . <br>
( default: 0.5 )
</li>
<br>
2021-01-20 21:00:34 +00:00
< a name = "numHistDays" > </a>
<li> <b> numHistDays </b> <br>
2021-03-18 10:06:40 +00:00
Anzahl der vergangenen Tage ( historische Daten ) die zur Autokorrektur der PV Vorhersage verwendet werden sofern
aktiviert . <br>
2021-03-14 06:53:16 +00:00
( default: 7 )
2020-12-13 17:29:15 +00:00
</li>
<br>
2021-03-20 14:14:18 +00:00
< a name = "rainFactorSlope" > </a>
<li> <b> rainFactorSlope </b> <br>
Prozentuale Berücksichtigung ( Steilheit ) der Regenprognose bei der solaren Vorhersage . <br>
Höhere Werte vermindern tendenziell den prognostizierten PV Ertrag , kleinere Werte erhöhen den prognostizierten PV
Ertrag tendenziell . <br>
( default: 20 )
</li>
<br>
2020-12-13 17:29:15 +00:00
< a name = "showDiff" > </a>
<li> <b> showDiff & lt ; no | top | bottom & gt ; </b> <br>
Zusätzliche Anzeige der Differenz "Ertrag - Verbrauch" wie beim Anzeigetyp Differential ( diff ) . <br>
( default: no )
</li>
<br>
< a name = "showHeader" > </a>
<li> <b> showHeader </b> <br>
Anzeige der Kopfzeile mit Prognosedaten , Rest des aktuellen Tages und des nächsten Tages <br>
( default: 1 )
</li>
<br>
< a name = "showLink" > </a>
<li> <b> showLink </b> <br>
Anzeige des Detail - Links über dem Grafik - Device <br>
( default: 1 )
</li>
<br>
< a name = "showNight" > </a>
<li> <b> showNight </b> <br>
Die Nachtstunden ( ohne Ertragsprognose ) werden mit angezeigt . <br>
( default: 0 )
</li>
<br>
< a name = "showWeather" > </a>
<li> <b> showWeather </b> <br>
Wettericons anzeigen . <br>
( default: 1 )
</li>
<br>
< a name = "spaceSize" > </a>
<li> <b> spaceSize & lt ; value & gt ; </b> <br>
Legt fest wieviel Platz in px über oder unter den Balken ( bei Anzeigetyp Differential ( diff ) ) zur Anzeige der
Werte freigehalten wird . Bei Styles mit große Fonts kann der default - Wert zu klein sein bzw . rutscht ein
Balken u . U . über die Grundlinie . In diesen Fällen bitte den Wert erhöhen . <br>
( default: 24 )
</li>
<br>
< a name = "Wh/kWh" > </a>
<li> <b> Wh /kWh <Wh | kWh> </ b > <br>
Definiert die Anzeigeeinheit in Wh oder in kWh auf eine Nachkommastelle gerundet . <br>
( default: W )
</li>
<br>
< a name = "weatherColor" > </a>
<li> <b> weatherColor </b> <br>
Farbe der Wetter - Icons .
</li>
2021-01-01 19:44:20 +00:00
<br>
< a name = "weatherColor_night" > </a>
<li> <b> weatherColor_night </b> <br>
Farbe der Wetter - Icons für die Nachtstunden .
</li>
2020-12-13 17:29:15 +00:00
<br>
</ul>
</ul>
</ul>
= end html_DE
= for : application / json ; q = META . json 76 _SolarForecast . pm
{
"abstract" : "Visualization of solar predictions for PV systems" ,
"x_lang" : {
"de" : {
"abstract" : "Visualisierung von solaren Vorhersagen für PV Anlagen"
}
} ,
"keywords" : [
"sma" ,
"photovoltaik" ,
"electricity" ,
"portal" ,
"smaportal" ,
"graphics" ,
"longpoll" ,
"refresh"
] ,
"version" : "v1.1.1" ,
"release_status" : "testing" ,
"author" : [
"Heiko Maaz <heiko.maaz@t-online.de>"
] ,
"x_fhem_maintainer" : [
"DS_Starter"
] ,
"x_fhem_maintainer_github" : [
"nasseeder1"
] ,
"prereqs" : {
"runtime" : {
"requires" : {
"FHEM" : 5.00918799 ,
"perl" : 5.014 ,
"Time::HiRes" : 0
} ,
"recommends" : {
"FHEM::Meta" : 0
} ,
"suggests" : {
}
}
} ,
"resources" : {
"repository" : {
"x_dev" : {
"type" : "svn" ,
"url" : "https://svn.fhem.de/trac/browser/trunk/fhem/contrib/DS_Starter" ,
"web" : "https://svn.fhem.de/trac/browser/trunk/fhem/contrib/DS_Starter/76_SolarForecast.pm" ,
"x_branch" : "dev" ,
"x_filepath" : "fhem/contrib/" ,
"x_raw" : "https://svn.fhem.de/fhem/trunk/fhem/contrib/DS_Starter/76_SolarForecast.pm"
}
}
}
}
= end : application / json ; q = META . json
= cut