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 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-06-03 16:05:30 +00:00
use FHEM::SynoModules::SMUtils qw(
evaljson
2021-01-17 19:13:02 +00:00
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-06-12 07:03:47 +00:00
CommandAttr
2021-05-11 16:42:32 +00:00
CommandSet
2021-05-30 07:54:58 +00:00
CommandSetReading
2021-01-17 19:13:02 +00:00
data
2020-12-13 17:29:15 +00:00
defs
delFromDevAttrList
delFromAttrList
devspec2array
deviceEvents
2021-03-24 09:07:17 +00:00
DoTrigger
2020-12-13 17:29:15 +00:00
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
2021-04-12 21:30:00 +00:00
NexthoursVal
2020-12-13 17:29:15 +00:00
)
) ;
}
# Versions History intern
my % vNotesIntern = (
2021-06-15 15:45:56 +00:00
"0.52.4" = > "15.06.2021 minor fix, possible avoid implausible inverter values " ,
2021-06-14 18:20:10 +00:00
"0.52.3" = > "14.06.2021 consumer on/off icon gray if no on/off command is defined, more consumer debug log " ,
2021-06-13 19:18:25 +00:00
"0.52.2" = > "13.06.2021 attr consumerAdviceIcon can be 'none', new attr debug, minor fixes, write consumers cachefile " ,
2021-06-12 12:32:43 +00:00
"0.52.1" = > "12.06.2021 change Attr Css behavior, new attr consumerAdviceIcon " ,
2021-06-12 07:03:47 +00:00
"0.52.0" = > "12.06.2021 new Attr Css " ,
2021-06-10 20:32:37 +00:00
"0.51.3" = > "10.06.2021 more refactoring, add 'none' to graphicSelect " ,
2021-06-05 09:36:40 +00:00
"0.51.2" = > "05.06.2021 minor fixes " ,
2021-06-04 09:19:18 +00:00
"0.51.1" = > "04.06.2021 minor fixes " ,
2021-06-03 16:05:30 +00:00
"0.51.0" = > "03.06.2021 some bugfixing, Calculation of PV correction factors refined, new setter plantConfiguration " .
"delete getter stringConfig " ,
2021-06-03 08:21:24 +00:00
"0.50.2" = > "02.06.2021 more refactoring, delete attr headerAlignment, consumerlegend as table " ,
"0.50.1" = > "02.06.2021 switch to mathematical rounding of cloudiness range " ,
2021-06-01 21:05:53 +00:00
"0.50.0" = > "01.06.2021 real switch off time in consumerXX_planned_stop when finished, change key 'ready' to 'auto' " .
"consider switch on Time limits (consumer keys notbefore/notafter) " ,
2021-06-01 10:46:44 +00:00
"0.49.5" = > "01.06.2021 change pv correction factor to 1 if no historical factors found (only with automatic correction) " ,
2021-06-01 08:10:36 +00:00
"0.49.4" = > "01.06.2021 fix wrong display at month change and using historyHour " ,
2021-05-31 17:16:40 +00:00
"0.49.3" = > "31.05.2021 improve calcPVforecast pvcorrfactor for multistring configuration " ,
2021-05-31 15:41:22 +00:00
"0.49.2" = > "31.05.2021 fix time calc in sub forecastGraphic " ,
2021-05-30 19:20:33 +00:00
"0.49.1" = > "30.05.2021 no consumer check during start Forum: https://forum.fhem.de/index.php/topic,117864.msg1159959.html#msg1159959 " ,
2021-05-30 07:54:58 +00:00
"0.49.0" = > "29.05.2021 consumer legend, attr consumerLegend, no negative val Current_SelfConsumption, Current_PV " ,
2021-05-28 11:45:28 +00:00
"0.48.0" = > "28.05.2021 new optional key ready in consumer attribute " ,
"0.47.0" = > "28.05.2021 add flowGraphic, attr flowGraphicSize, graphicSelect, flowGraphicAnimate " ,
2021-05-21 09:35:17 +00:00
"0.46.1" = > "21.05.2021 set <> reset pvHistory <day> <hour> " ,
2021-05-16 19:37:54 +00:00
"0.46.0" = > "16.05.2021 integrate intotal, outtotal to currentBatteryDev, set maxconsumer to 9 " ,
2021-05-15 08:37:12 +00:00
"0.45.1" = > "13.05.2021 change the calc of etotal at the beginning of every hour in _transferInverterValues " .
"fix createNotifyDev for currentBatteryDev " ,
2021-05-12 13:17:21 +00:00
"0.45.0" = > "12.05.2021 integrate consumptionForecast to graphic, change beamXContent to pvForecast, pvReal " ,
2021-05-11 16:42:32 +00:00
"0.44.0" = > "10.05.2021 consumptionForecast for attr beamXContent, consumer are switched on/off " ,
2021-05-09 18:29:53 +00:00
"0.43.0" = > "08.05.2021 plan Consumers " ,
2021-05-02 20:24:39 +00:00
"0.42.0" = > "01.05.2021 new attr consumerXX, currentMeterDev is mandatory, new getter valConsumerMaster " .
"new commandref ancor syntax " ,
2021-04-28 16:09:16 +00:00
"0.41.0" = > "28.04.2021 _estConsumptionForecast: implement Smoothing difference " ,
2021-04-25 15:33:22 +00:00
"0.40.0" = > "25.04.2021 change checkdwdattr, new attr follow70percentRule " ,
2021-04-24 18:05:34 +00:00
"0.39.0" = > "24.04.2021 new attr sameWeekdaysForConsfc, readings Current_SelfConsumption, Current_SelfConsumptionRate, " .
"Current_AutarkyRate " ,
2021-04-23 18:06:08 +00:00
"0.38.3" = > "21.04.2021 minor fixes in sub calcVariance, Traffic light indicator for prediction quality, some more fixes " ,
2021-04-24 07:37:41 +00:00
"0.38.2" = > "20.04.2021 fix _estConsumptionForecast, add consumption values to graphic " ,
2021-04-19 17:28:14 +00:00
"0.38.1" = > "19.04.2021 bug fixing " ,
2021-04-18 16:37:33 +00:00
"0.38.0" = > "18.04.2021 consumption forecast for the next hours prepared " ,
2021-04-24 07:37:41 +00:00
"0.37.0" = > "17.04.2021 _estConsumptionForecast, new getter forecastQualities, new setter currentRadiationDev " .
2021-04-17 07:37:23 +00:00
"language sensitive setup hints " ,
2021-04-14 17:12:33 +00:00
"0.36.1" = > "14.04.2021 add dayname to pvHistory " ,
2021-04-14 12:18:26 +00:00
"0.36.0" = > "14.04.2021 add con to pvHistory, add quality info to pvCircular, new reading nextPolltime " ,
2021-04-12 20:08:26 +00:00
"0.35.0" = > "12.04.2021 create additional PVforecast events - PV forecast until the end of the coming day " ,
2021-04-11 07:54:28 +00:00
"0.34.1" = > "11.04.2021 further improvement of cloud dependent calculation autocorrection " ,
2021-04-10 08:31:37 +00:00
"0.34.0" = > "10.04.2021 only hours with the same cloud cover range are considered for pvCorrection, some fixes " ,
2021-04-09 21:09:20 +00:00
"0.33.0" = > "09.04.2021 new setter currentBatteryDev, bugfix in _transferMeterValues " ,
2021-04-09 16:11:17 +00:00
"0.32.0" = > "09.04.2021 currentMeterDev can have: gcon=-gfeedin " ,
2021-04-07 16:05:08 +00:00
"0.31.1" = > "07.04.2021 write new values to pvhistory, change CO to Current_Consumption in graphic " ,
2021-04-06 15:50:16 +00:00
"0.31.0" = > "06.04.2021 extend currentMeterDev by gfeedin, feedtotal " ,
2021-04-05 14:54:45 +00:00
"0.30.0" = > "05.04.2021 estimate readings to the minute in sub _calcSummaries, new setter energyH4Trigger " ,
2021-04-04 06:40:40 +00:00
"0.29.0" = > "03.04.2021 new setter powerTrigger " ,
2021-04-03 08:36:11 +00:00
"0.28.0" = > "03.04.2021 new attributes beam1FontColor, beam2FontColor, rename/new some readings " ,
2021-04-02 19:58:48 +00:00
"0.27.0" = > "02.04.2021 additional readings " ,
2021-04-04 06:40:40 +00:00
"0.26.0" = > "02.04.2021 rename attr maxPV to maxValBeam, bugfix in _additionalActivities " ,
2021-03-28 08:24:48 +00:00
"0.25.0" = > "28.03.2021 changes regarding perlcritic, new getter valCurrent " ,
2021-03-26 12:03:05 +00:00
"0.24.0" = > "26.03.2021 the language setting of the system is taken into account in the weather texts " .
"rename weatherColor_night to weatherColorNight, history_hour to historyHour " ,
2021-03-25 19:50:14 +00:00
"0.23.0" = > "25.03.2021 change attr layoutType, fix calc reading Today_PVforecast " ,
2021-03-25 17:38:19 +00:00
"0.22.0" = > "25.03.2021 event management, move DWD values one hour to the future, some more corrections " ,
2021-03-24 09:07:17 +00:00
"0.21.0" = > "24.03.2021 event management " ,
2021-03-23 22:01:47 +00:00
"0.20.0" = > "23.03.2021 new sub CircularVal, NexthoursVal, some fixes " ,
2021-03-22 20:33:54 +00:00
"0.19.0" = > "22.03.2021 new sub HistoryVal, some fixes " ,
"0.18.0" = > "21.03.2021 implement sub forecastGraphic from Wzut " ,
2021-03-21 15:26:26 +00:00
"0.17.1" = > "21.03.2021 bug fixes, delete Helper->NextHour " ,
2021-03-25 18:12:26 +00:00
"0.17.0" = > "20.03.2021 new attr cloudFactorDamping / rainFactorDamping, 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-04-17 07:37:23 +00:00
currentRadiationDev = > { fn = > \ & _setcurrentRadiationDev } ,
2021-01-27 17:02:18 +00:00
modulePeakString = > { fn = > \ & _setmodulePeakString } ,
2021-01-26 20:38:22 +00:00
inverterStrings = > { fn = > \ & _setinverterStrings } ,
2021-05-30 07:54:58 +00:00
consumerAction = > { fn = > \ & _setconsumerAction } ,
2020-12-27 18:51:53 +00:00
currentInverterDev = > { fn = > \ & _setinverterDevice } ,
currentMeterDev = > { fn = > \ & _setmeterDevice } ,
2021-04-09 21:09:20 +00:00
currentBatteryDev = > { fn = > \ & _setbatteryDevice } ,
2021-04-05 14:54:45 +00:00
energyH4Trigger = > { fn = > \ & _setenergyH4Trigger } ,
2021-06-03 16:05:30 +00:00
plantConfiguration = > { fn = > \ & _setplantConfiguration } ,
2021-04-04 06:40:40 +00:00
powerTrigger = > { fn = > \ & _setpowerTrigger } ,
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-05-02 20:24:39 +00:00
data = > { fn = > \ & _getdata , needcred = > 0 } ,
html = > { fn = > \ & _gethtml , needcred = > 0 } ,
ftui = > { fn = > \ & _getftui , needcred = > 0 } ,
valCurrent = > { fn = > \ & _getlistCurrent , needcred = > 0 } ,
valConsumerMaster = > { fn = > \ & _getlistvalConsumerMaster , needcred = > 0 } ,
pvHistory = > { fn = > \ & _getlistPVHistory , needcred = > 0 } ,
pvCircular = > { fn = > \ & _getlistPVCircular , needcred = > 0 } ,
forecastQualities = > { fn = > \ & _getForecastQualities , needcred = > 0 } ,
nextHours = > { fn = > \ & _getlistNextHours , needcred = > 0 } ,
) ;
my % hattr = ( # Hash für Attr-Funktion
2021-05-12 11:12:59 +00:00
consumer = > { fn = > \ & _attrconsumer } ,
2021-01-17 19:13:02 +00:00
) ;
2021-06-11 07:32:00 +00:00
my % htr = ( # Hash even/odd für <tr>
0 = > { cl = > 'even' } ,
1 = > { cl = > 'odd' } ,
) ;
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 } ,
2021-05-02 20:24:39 +00:00
) ; # mt = default mintime (Minuten)
2020-12-13 17:29:15 +00:00
2021-06-03 16:05:30 +00:00
my % hqtxt = ( # Hash (Setup) Texte
cfd = > { EN = > qq{ Please select the Weather forecast device with "set LINK currentForecastDev" } ,
DE = > qq{ Bitte geben sie das Wettervorhersage Device mit "set LINK currentForecastDev" an } } ,
crd = > { EN = > qq{ Please select the Radiation forecast device with "set LINK currentRadiationDev" } ,
DE = > qq{ Bitte geben sie das Strahlungsvorhersage Device mit "set LINK currentRadiationDev" an } } ,
cid = > { EN = > qq{ Please specify the Inverter device with "set LINK currentInverterDev" } ,
DE = > qq{ Bitte geben sie das Wechselrichter Device mit "set LINK currentInverterDev" an } } ,
mid = > { EN = > qq{ Please specify the device for energy measurement with "set LINK currentMeterDev" } ,
DE = > qq{ Bitte geben sie das Device zur Energiemessung mit "set LINK currentMeterDev" an } } ,
ist = > { EN = > qq{ Please define all of your used string names with "set LINK inverterStrings" } ,
DE = > qq{ Bitte geben sie alle von Ihnen verwendeten Stringnamen mit "set LINK inverterStrings" an } } ,
mps = > { EN = > qq{ Please specify the total peak power for every string with "set LINK modulePeakString" } ,
DE = > qq{ Bitte geben sie die Gesamtspitzenleistung von jedem String mit "set LINK modulePeakString" an } } ,
mdr = > { EN = > qq{ Please specify the module direction with "set LINK moduleDirection" } ,
DE = > qq{ Bitte geben Sie die Modulausrichtung mit "set LINK moduleDirection" an } } ,
mta = > { EN = > qq{ Please specify the module tilt angle with "set LINK moduleTiltAngle" } ,
DE = > qq{ Bitte geben Sie den Modulneigungswinkel mit "set LINK moduleTiltAngle" an } } ,
2021-06-10 20:32:37 +00:00
awd = > { EN = > qq{ Waiting for solar forecast data ... } ,
DE = > qq{ Warten auf Solarvorhersagedaten ... } } ,
2021-06-13 14:11:26 +00:00
cnsm = > { EN = > qq{ Consumer } ,
DE = > qq{ Verbraucher } } ,
eiau = > { EN = > qq{ On/Off } ,
DE = > qq{ Ein/Aus } } ,
auto = > { EN = > qq{ Auto } ,
DE = > qq{ Auto } } ,
pstate = > { EN = > qq{ Planning status: <pstate><br>On: <start><br>Off: <stop> } ,
DE = > qq{ Planungsstatus: <pstate><br>Ein: <start><br>Aus: <stop> } } ,
2021-06-03 16:05:30 +00:00
strok = > { EN = > qq{ Congratulations 😊, your string configuration checked without found errors ! } ,
DE = > qq{ Herzlichen Glückwunsch 😊, Ihre String-Konfiguration wurde ohne gefundene Fehler geprüft! } } ,
strnok = > { EN = > qq{ Oh no 🙁, your string configuration is inconsistent. \ nPlease check the settings of modulePeakString, moduleDirection, moduleTiltAngle ! } ,
DE = > qq{ Oh nein 🙁, Ihre String-Konfiguration ist inkonsistent. \ nBitte überprüfen Sie die Einstellungen von modulePeakString, moduleDirection, moduleTiltAngle ! } } ,
2021-04-17 07:37:23 +00:00
) ;
2021-06-13 14:11:26 +00:00
my % htitles = ( # Hash Hilfetexte (Mouse Over)
2021-06-13 09:43:58 +00:00
iaaf = > { EN = > qq{ Automatic mode off -> Enable automatic mode } ,
2021-06-13 14:11:26 +00:00
DE = > qq{ Automatikmodus aus -> Automatik freigeben } } ,
2021-06-13 09:43:58 +00:00
ieas = > { EN = > qq{ Automatic mode on -> Lock automatic mode } ,
2021-06-13 14:11:26 +00:00
DE = > qq{ Automatikmodus ein -> Automatik sperren } } ,
2021-06-13 09:43:58 +00:00
iave = > { EN = > qq{ Off -> Switch on consumer } ,
2021-06-13 14:11:26 +00:00
DE = > qq{ Aus -> Verbraucher einschalten } } ,
2021-06-14 18:20:10 +00:00
ians = > { EN = > qq{ Off -> no on-command defined! } ,
DE = > qq{ Aus -> kein on-Kommando definiert! } } ,
2021-06-13 09:43:58 +00:00
ieva = > { EN = > qq{ On -> Switch off consumer } ,
2021-06-13 14:11:26 +00:00
DE = > qq{ Ein -> Verbraucher ausschalten } } ,
2021-06-14 18:20:10 +00:00
iens = > { EN = > qq{ On -> no off-command defined! } ,
DE = > qq{ Ein -> kein off-Kommando definiert! } } ,
2021-06-13 09:43:58 +00:00
upd = > { EN = > qq{ Update } ,
2021-06-13 14:11:26 +00:00
DE = > qq{ Update } } ,
2021-06-13 09:43:58 +00:00
on = > { EN = > qq{ switched on } ,
2021-06-13 14:11:26 +00:00
DE = > qq{ eingeschaltet } } ,
2021-06-13 09:43:58 +00:00
off = > { EN = > qq{ switched off } ,
2021-06-13 14:11:26 +00:00
DE = > qq{ ausgeschaltet } } ,
2021-06-13 09:43:58 +00:00
undef = > { EN = > qq{ undefined } ,
2021-06-13 14:11:26 +00:00
DE = > qq{ undefiniert } } ,
2021-06-13 09:43:58 +00:00
dela = > { EN = > qq{ delayed } ,
2021-06-13 14:11:26 +00:00
DE = > qq{ verzoegert } } ,
2021-06-13 09:43:58 +00:00
conrec = > { EN = > qq{ Current time is within the consumption planning } ,
2021-06-13 14:11:26 +00:00
DE = > qq{ Aktuelle Zeit liegt innerhalb der Verbrauchsplanung } } ,
2021-06-13 09:43:58 +00:00
connorec = > { EN = > qq{ Consumption planning is outside current time } ,
2021-06-13 14:11:26 +00:00
DE = > qq{ Verbrauchsplanung liegt ausserhalb aktueller Zeit } } ,
pstate = > { EN = > qq{ Planning status: <pstate> \ n \ nOn: <start> \ nOff: <stop> } ,
DE = > qq{ Planungsstatus: <pstate> \ n \ nEin: <start> \ nAus: <stop> } } ,
2021-05-30 07:54:58 +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-26 12:03:05 +00:00
'0' = > { s = > '0' , icon = > 'weather_sun' , txtd = > 'sonnig' , txte = > 'sunny' } ,
'1' = > { s = > '0' , icon = > 'weather_cloudy_light' , txtd = > 'Bewölkung abnehmend' , txte = > 'Cloudiness decreasing' } ,
'2' = > { s = > '0' , icon = > 'weather_cloudy' , txtd = > 'Bewölkung unverändert' , txte = > 'Cloudiness unchanged' } ,
'3' = > { s = > '0' , icon = > 'weather_cloudy_heavy' , txtd = > 'Bewölkung zunehmend' , txte = > 'Cloudiness increasing' } ,
'4' = > { s = > '0' , icon = > 'unknown' , txtd = > 'Sicht durch Rauch oder Asche vermindert' , txte = > 'Visibility reduced by smoke or ash' } ,
'5' = > { s = > '0' , icon = > 'unknown' , txtd = > 'trockener Dunst (relative Feuchte < 80 %)' , txte = > 'dry haze (relative humidity < 80 %)' } ,
'6' = > { s = > '0' , icon = > 'unknown' , txtd = > 'verbreiteter Schwebstaub, nicht vom Wind herangeführt' , txte = > 'widespread airborne dust, not brought in by the wind' } ,
'7' = > { s = > '0' , icon = > 'unknown' , txtd = > 'Staub oder Sand bzw. Gischt, vom Wind herangeführt' , txte = > 'Dust or sand or spray, brought in by the wind' } ,
'8' = > { s = > '0' , icon = > 'unknown' , txtd = > 'gut entwickelte Staub- oder Sandwirbel' , txte = > 'well-developed dust or sand vortex' } ,
'9' = > { s = > '0' , icon = > 'unknown' , txtd = > 'Staub- oder Sandsturm im Gesichtskreis, aber nicht an der Station' , txte = > 'Dust or sand storm in the visual circle, but not at the station' } ,
'10' = > { s = > '0' , icon = > 'weather_fog' , txtd = > 'Nebel' , txte = > 'Fog' } ,
'11' = > { s = > '0' , icon = > 'weather_rain_fog' , txtd = > 'Nebel mit Regen' , txte = > 'Fog with rain' } ,
'12' = > { s = > '0' , icon = > 'weather_fog' , txtd = > 'durchgehender Bodennebel' , txte = > 'continuous ground fog' } ,
'13' = > { s = > '0' , icon = > 'unknown' , txtd = > 'Wetterleuchten sichtbar, kein Donner gehört' , txte = > 'Weather light visible, no thunder heard' } ,
'14' = > { s = > '0' , icon = > 'unknown' , txtd = > 'Niederschlag im Gesichtskreis, nicht den Boden erreichend' , txte = > 'Precipitation in the visual circle, not reaching the ground' } ,
'15' = > { s = > '0' , icon = > 'unknown' , txtd = > 'Niederschlag in der Ferne (> 5 km), aber nicht an der Station' , txte = > 'Precipitation in the distance (> 5 km), but not at the station' } ,
'16' = > { s = > '0' , icon = > 'unknown' , txtd = > 'Niederschlag in der Nähe (< 5 km), aber nicht an der Station' , txte = > 'Precipitation in the vicinity (< 5 km), but not at the station' } ,
'17' = > { s = > '0' , icon = > 'unknown' , txtd = > 'Gewitter (Donner hörbar), aber kein Niederschlag an der Station' , txte = > 'Thunderstorm (thunder audible), but no precipitation at the station' } ,
'18' = > { s = > '0' , icon = > 'unknown' , txtd = > 'Markante Böen im Gesichtskreis, aber kein Niederschlag an der Station' , txte = > 'marked gusts in the visual circle, but no precipitation at the station' } ,
'19' = > { s = > '0' , icon = > 'unknown' , txtd = > 'Tromben (trichterförmige Wolkenschläuche) im Gesichtskreis' , txte = > 'Trombles (funnel-shaped cloud tubes) in the circle of vision' } ,
'20' = > { s = > '0' , icon = > 'unknown' , txtd = > 'nach Sprühregen oder Schneegriesel' , txte = > 'after drizzle or snow drizzle' } ,
'21' = > { s = > '0' , icon = > 'unknown' , txtd = > 'nach Regen' , txte = > 'after rain' } ,
'22' = > { s = > '0' , icon = > 'unknown' , txtd = > 'nach Schnefall' , txte = > 'after snowfall' } ,
'23' = > { s = > '0' , icon = > 'unknown' , txtd = > 'nach Schneeregen oder Eiskörnern' , txte = > 'after sleet or ice grains' } ,
'24' = > { s = > '0' , icon = > 'unknown' , txtd = > 'nach gefrierendem Regen' , txte = > 'after freezing rain' } ,
'25' = > { s = > '0' , icon = > 'unknown' , txtd = > 'nach Regenschauer' , txte = > 'after rain shower' } ,
'26' = > { s = > '0' , icon = > 'unknown' , txtd = > 'nach Schneeschauer' , txte = > 'after snow shower' } ,
'27' = > { s = > '0' , icon = > 'unknown' , txtd = > 'nach Graupel- oder Hagelschauer' , txte = > 'after sleet or hail showers' } ,
'28' = > { s = > '0' , icon = > 'unknown' , txtd = > 'nach Nebel' , txte = > 'after fog' } ,
'29' = > { s = > '0' , icon = > 'unknown' , txtd = > 'nach Gewitter' , txte = > 'after thunderstorm' } ,
'30' = > { s = > '0' , icon = > 'unknown' , txtd = > 'leichter oder mäßiger Sandsturm, an Intensität abnehmend' , txte = > 'light or moderate sandstorm, decreasing in intensity' } ,
'31' = > { s = > '0' , icon = > 'unknown' , txtd = > 'leichter oder mäßiger Sandsturm, unveränderte Intensität' , txte = > 'light or moderate sandstorm, unchanged intensity' } ,
'32' = > { s = > '0' , icon = > 'unknown' , txtd = > 'leichter oder mäßiger Sandsturm, an Intensität zunehmend' , txte = > 'light or moderate sandstorm, increasing in intensity' } ,
'33' = > { s = > '0' , icon = > 'unknown' , txtd = > 'schwerer Sandsturm, an Intensität abnehmend' , txte = > 'heavy sandstorm, decreasing in intensity' } ,
'34' = > { s = > '0' , icon = > 'unknown' , txtd = > 'schwerer Sandsturm, unveränderte Intensität' , txte = > 'heavy sandstorm, unchanged intensity' } ,
'35' = > { s = > '0' , icon = > 'unknown' , txtd = > 'schwerer Sandsturm, an Intensität zunehmend' , txte = > 'heavy sandstorm, increasing in intensity' } ,
'36' = > { s = > '0' , icon = > 'weather_snow_light' , txtd = > 'leichtes oder mäßiges Schneefegen, unter Augenhöhe' , txte = > 'light or moderate snow sweeping, below eye level' } ,
'37' = > { s = > '0' , icon = > 'weather_snow_heavy' , txtd = > 'starkes Schneefegen, unter Augenhöhe' , txte = > 'heavy snow sweeping, below eye level' } ,
'38' = > { s = > '0' , icon = > 'weather_snow_light' , txtd = > 'leichtes oder mäßiges Schneetreiben, über Augenhöhe' , txte = > 'light or moderate blowing snow, above eye level' } ,
'39' = > { s = > '0' , icon = > 'weather_snow_heavy' , txtd = > 'starkes Schneetreiben, über Augenhöhe' , txte = > 'heavy snow drifting, above eye level' } ,
'40' = > { s = > '0' , icon = > 'weather_fog' , txtd = > 'Nebel in einiger Entfernung' , txte = > 'Fog in some distance' } ,
'41' = > { s = > '0' , icon = > 'weather_fog' , txtd = > 'Nebel in Schwaden oder Bänken' , txte = > 'Fog in swaths or banks' } ,
'42' = > { s = > '0' , icon = > 'weather_fog' , txtd = > 'Nebel, Himmel erkennbar, dünner werdend' , txte = > 'Fog, sky recognizable, thinning' } ,
'43' = > { s = > '0' , icon = > 'weather_fog' , txtd = > 'Nebel, Himmel nicht erkennbar, dünner werdend' , txte = > 'Fog, sky not recognizable, thinning' } ,
'44' = > { s = > '0' , icon = > 'weather_fog' , txtd = > 'Nebel, Himmel erkennbar, unverändert' , txte = > 'Fog, sky recognizable, unchanged' } ,
'45' = > { s = > '1' , icon = > 'weather_fog' , txtd = > 'Nebel' , txte = > 'Fog' } ,
'46' = > { s = > '0' , icon = > 'weather_fog' , txtd = > 'Nebel, Himmel erkennbar, dichter werdend' , txte = > 'Fog, sky recognizable, becoming denser' } ,
'47' = > { s = > '0' , icon = > 'weather_fog' , txtd = > 'Nebel, Himmel nicht erkennbar, dichter werdend' , txte = > 'Fog, sky not visible, becoming denser' } ,
'48' = > { s = > '1' , icon = > 'weather_fog' , txtd = > 'Nebel mit Reifbildung' , txte = > 'Fog with frost formation' } ,
'49' = > { s = > '0' , icon = > 'weather_fog' , txtd = > 'Nebel mit Reifansatz, Himmel nicht erkennbar' , txte = > 'Fog with frost, sky not visible' } ,
'50' = > { s = > '0' , icon = > 'weather_rain' , txtd = > 'unterbrochener leichter Sprühregen' , txte = > 'intermittent light drizzle' } ,
'51' = > { s = > '1' , icon = > 'weather_rain_light' , txtd = > 'leichter Sprühregen' , txte = > 'light drizzle' } ,
'52' = > { s = > '0' , icon = > 'weather_rain' , txtd = > 'unterbrochener mäßiger Sprühregen' , txte = > 'intermittent moderate drizzle' } ,
'53' = > { s = > '1' , icon = > 'weather_rain_light' , txtd = > 'leichter Sprühregen' , txte = > 'light drizzle' } ,
'54' = > { s = > '0' , icon = > 'weather_rain_heavy' , txtd = > 'unterbrochener starker Sprühregen' , txte = > 'intermittent heavy drizzle' } ,
'55' = > { s = > '1' , icon = > 'weather_rain_heavy' , txtd = > 'starker Sprühregen' , txte = > 'heavy drizzle' } ,
'56' = > { s = > '1' , icon = > 'weather_rain_light' , txtd = > 'leichter gefrierender Sprühregen' , txte = > 'light freezing drizzle' } ,
'57' = > { s = > '1' , icon = > 'weather_rain_heavy' , txtd = > 'mäßiger oder starker gefrierender Sprühregen' , txte = > 'moderate or heavy freezing drizzle' } ,
'58' = > { s = > '0' , icon = > 'weather_rain_light' , txtd = > 'leichter Sprühregen mit Regen' , txte = > 'light drizzle with rain' } ,
'59' = > { s = > '0' , icon = > 'weather_rain_heavy' , txtd = > 'mäßiger oder starker Sprühregen mit Regen' , txte = > 'moderate or heavy drizzle with rain' } ,
'60' = > { s = > '0' , icon = > 'weather_rain_light' , txtd = > 'unterbrochener leichter Regen oder einzelne Regentropfen' , txte = > 'intermittent light rain or single raindrops' } ,
'61' = > { s = > '1' , icon = > 'weather_rain_light' , txtd = > 'leichter Regen' , txte = > 'light rain' } ,
'62' = > { s = > '0' , icon = > 'weather_rain' , txtd = > 'unterbrochener mäßiger Regen' , txte = > 'intermittent moderate rain' } ,
'63' = > { s = > '1' , icon = > 'weather_rain' , txtd = > 'mäßiger Regen' , txte = > 'moderate rain' } ,
'64' = > { s = > '0' , icon = > 'weather_rain_heavy' , txtd = > 'unterbrochener starker Regen' , txte = > 'intermittent heavy rain' } ,
'65' = > { s = > '1' , icon = > 'weather_rain_heavy' , txtd = > 'starker Regen' , txte = > 'heavy rain' } ,
'66' = > { s = > '1' , icon = > 'weather_rain_snow_light' , txtd = > 'leichter gefrierender Regen' , txte = > 'light freezing rain' } ,
'67' = > { s = > '1' , icon = > 'weather_rain_snow_heavy' , txtd = > 'mäßiger oder starker gefrierender Regen' , txte = > 'moderate or heavy freezing rain' } ,
'68' = > { s = > '0' , icon = > 'weather_rain_snow_light' , txtd = > 'leichter Schneeregen' , txte = > 'light sleet' } ,
'69' = > { s = > '0' , icon = > 'weather_rain_snow_heavy' , txtd = > 'mäßiger oder starker Schneeregen' , txte = > 'moderate or heavy sleet' } ,
'70' = > { s = > '0' , icon = > 'weather_snow_light' , txtd = > 'unterbrochener leichter Schneefall oder einzelne Schneeflocken' , txte = > 'intermittent light snowfall or single snowflakes' } ,
'71' = > { s = > '1' , icon = > 'weather_snow_light' , txtd = > 'leichter Schneefall' , txte = > 'light snowfall' } ,
'72' = > { s = > '0' , icon = > 'weather_snow' , txtd = > 'unterbrochener mäßiger Schneefall' , txte = > 'intermittent moderate snowfall' } ,
'73' = > { s = > '1' , icon = > 'weather_snow' , txtd = > 'mäßiger Schneefall' , txte = > 'moderate snowfall' } ,
'74' = > { s = > '0' , icon = > 'weather_snow_heavy' , txtd = > 'unterbrochener starker Schneefall' , txte = > 'intermittent heavy snowfall' } ,
'75' = > { s = > '1' , icon = > 'weather_snow_heavy' , txtd = > 'starker Schneefall' , txte = > 'heavy snowfall' } ,
'76' = > { s = > '0' , icon = > 'weather_frost' , txtd = > 'Eisnadeln (Polarschnee)' , txte = > 'Ice needles (polar snow)' } ,
'77' = > { s = > '1' , icon = > 'weather_frost' , txtd = > 'Schneegriesel' , txte = > 'Snow drizzle' } ,
'78' = > { s = > '0' , icon = > 'weather_frost' , txtd = > 'Schneekristalle' , txte = > 'Snow crystals' } ,
'79' = > { s = > '0' , icon = > 'weather_frost' , txtd = > 'Eiskörner (gefrorene Regentropfen)' , txte = > 'Ice grains (frozen raindrops)' } ,
'80' = > { s = > '1' , icon = > 'weather_rain_light' , txtd = > 'leichter Regenschauer' , txte = > 'light rain shower' } ,
'81' = > { s = > '1' , icon = > 'weather_rain' , txtd = > 'mäßiger oder starkerRegenschauer' , txte = > 'moderate or heavy rain shower' } ,
'82' = > { s = > '1' , icon = > 'weather_rain_heavy' , txtd = > 'sehr starker Regenschauer' , txte = > 'very heavy rain shower' } ,
'83' = > { s = > '0' , icon = > 'weather_snow' , txtd = > 'mäßiger oder starker Schneeregenschauer' , txte = > 'moderate or heavy sleet shower' } ,
'84' = > { s = > '0' , icon = > 'weather_snow_light' , txtd = > 'leichter Schneeschauer' , txte = > 'light snow shower' } ,
'85' = > { s = > '1' , icon = > 'weather_snow_light' , txtd = > 'leichter Schneeschauer' , txte = > 'light snow shower' } ,
'86' = > { s = > '1' , icon = > 'weather_snow_heavy' , txtd = > 'mäßiger oder starker Schneeschauer' , txte = > 'moderate or heavy snow shower' } ,
'87' = > { s = > '0' , icon = > 'weather_snow_heavy' , txtd = > 'mäßiger oder starker Graupelschauer' , txte = > 'moderate or heavy sleet shower' } ,
'88' = > { s = > '0' , icon = > 'unknown' , txtd = > 'leichter Hagelschauer' , txte = > 'light hailstorm' } ,
'89' = > { s = > '0' , icon = > 'unknown' , txtd = > 'mäßiger oder starker Hagelschauer' , txte = > 'moderate or heavy hailstorm' } ,
'90' = > { s = > '0' , icon = > 'weather_thunderstorm' , txtd = > '' , txte = > '' } ,
'91' = > { s = > '0' , icon = > 'weather_storm' , txtd = > '' , txte = > '' } ,
'92' = > { s = > '0' , icon = > 'weather_thunderstorm' , txtd = > '' , txte = > '' } ,
'93' = > { s = > '0' , icon = > 'weather_thunderstorm' , txtd = > '' , txte = > '' } ,
'94' = > { s = > '0' , icon = > 'weather_thunderstorm' , txtd = > '' , txte = > '' } ,
'95' = > { s = > '1' , icon = > 'weather_thunderstorm' , txtd = > 'leichtes oder mäßiges Gewitter ohne Graupel oder Hagel' , txte = > 'light or moderate thunderstorm without sleet or hail' } ,
'96' = > { s = > '1' , icon = > 'weather_storm' , txtd = > 'starkes Gewitter ohne Graupel oder Hagel,Gewitter mit Graupel oder Hagel' , txte = > 'strong thunderstorm without sleet or hail,thunderstorm with sleet or hail' } ,
'97' = > { s = > '0' , icon = > 'weather_storm' , txtd = > 'starkes Gewitter mit Regen oder Schnee' , txte = > 'heavy thunderstorm with rain or snow' } ,
'98' = > { s = > '0' , icon = > 'weather_storm' , txtd = > 'starkes Gewitter mit Sandsturm' , txte = > 'strong thunderstorm with sandstorm' } ,
'99' = > { s = > '1' , icon = > 'weather_storm' , txtd = > 'starkes Gewitter mit Graupel oder Hagel' , txte = > 'strong thunderstorm with sleet or hail' } ,
'100' = > { s = > '0' , icon = > 'weather_night' , txtd = > 'sternenklarer Himmel' , txte = > 'starry sky' } ,
2021-01-04 20:25:02 +00:00
) ;
2021-05-27 20:23:40 +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
my $ defslidenum = 3 ; # max. Anzahl der Arrayelemente in Schieberegistern
2021-03-13 14:48:27 +00:00
2021-05-27 20:23:40 +00:00
my $ pvhcache = $ attr { global } { modpath } . "/FHEM/FhemUtils/PVH_SolarForecast_" ; # Filename-Fragment für PV History (wird mit Devicename ergänzt)
my $ pvccache = $ attr { global } { modpath } . "/FHEM/FhemUtils/PVC_SolarForecast_" ; # Filename-Fragment für PV Circular (wird mit Devicename ergänzt)
2021-06-03 16:05:30 +00:00
my $ plantcfg = $ attr { global } { modpath } . "/FHEM/FhemUtils/PVCfg_SolarForecast_" ; # Filename-Fragment für PV Anlagenkonfiguration (wird mit Devicename ergänzt)
2021-06-13 19:18:25 +00:00
my $ csmcache = $ attr { global } { modpath } . "/FHEM/FhemUtils/PVCsm_SolarForecast_" ; # Filename-Fragment für Consumer Status (wird mit Devicename ergänzt)
2021-03-13 14:48:27 +00:00
2021-05-27 20:23:40 +00:00
my $ calcmaxd = 30 ; # Anzahl Tage die zur Berechnung Vorhersagekorrektur verwendet werden
my @ dweattrmust = qw( TTT Neff R101 ww SunUp SunRise SunSet ) ; # Werte die im Attr forecastProperties des Weather-DWD_Opendata Devices mindestens gesetzt sein müssen
my @ draattrmust = qw( Rad1h ) ; # Werte die im Attr forecastProperties des Radiation-DWD_Opendata Devices mindestens gesetzt sein müssen
my $ whistrepeat = 900 ; # Wiederholungsintervall Schreiben historische Daten
2021-01-22 20:16:33 +00:00
2021-05-27 20:23:40 +00:00
my $ cldampdef = 35 ; # Dämpfung (%) des Korrekturfaktors bzgl. effektiver Bewölkung, siehe: https://www.energie-experten.org/erneuerbare-energien/photovoltaik/planung/sonnenstunden
my $ cloud_base = 0 ; # Fußpunktverschiebung bzgl. effektiver Bewölkung
2021-01-17 19:13:02 +00:00
2021-05-27 20:23:40 +00:00
my $ rdampdef = 10 ; # Dämpfung (%) des Korrekturfaktors bzgl. Niederschlag (R101)
my $ rain_base = 0 ; # Fußpunktverschiebung bzgl. effektiver Bewölkung
2021-01-22 20:16:33 +00:00
2021-05-27 20:23:40 +00:00
my $ maxconsumer = 9 ; # maximale Anzahl der möglichen Consumer (Attribut)
my @ ctypes = qw( dishwasher dryer washingmachine heater other ) ; # erlaubte Consumer Typen
my $ defmintime = 60 ; # default min. Einschalt- bzw. Zykluszeit in Minuten
my $ defctype = "other" ; # default Verbrauchertyp
my $ defcmode = "can" ; # default Planungsmode der Verbraucher
2021-06-12 12:32:43 +00:00
my $ caicondef = 'light_light_dim_100@gold' ; # default consumerAdviceIcon
2021-05-27 20:23:40 +00:00
my $ defflowGSize = 300 ; # default flowGraphicSize
2021-05-02 20:24:39 +00:00
2021-06-12 07:03:47 +00:00
# Default CSS-Style
my $ cssdef = qq{ .flowg.text { stroke: none; fill: gray; } \ n } .
qq{ .flowg.sun_active { stroke: orange; fill: orange; } \ n } .
qq{ .flowg.sun_inactive { stroke: gray; fill: gray; } \ n } .
qq{ .flowg.bat25 { stroke: red; fill: red; } \ n } .
qq{ .flowg.bat50 { stroke: yellow; fill: yellow; } \ n } .
qq{ .flowg.bat75 { stroke: green; fill: green; } \ n } .
qq{ .flowg.grid_color1 { fill: green; } \ n } .
qq{ .flowg.grid_color2 { fill: red; } \ n } .
qq{ .flowg.grid_color3 { fill: gray; } \ n } .
qq{ .flowg.inactive_in { stroke: gray; stroke-dashoffset: 20; stroke-dasharray: 10; opacity: 0.2; } \ n } .
qq{ .flowg.inactive_out { stroke: gray; stroke-dashoffset: 20; stroke-dasharray: 10; opacity: 0.2; } \ n } .
qq{ .flowg.active_in { stroke: red; stroke-dashoffset: 20; stroke-dasharray: 10; opacity: 0.8; animation: dash 0.5s linear; animation-iteration-count: infinite; } \ n } .
qq{ .flowg.active_out { stroke: yellow; stroke-dashoffset: 20; stroke-dasharray: 10; opacity: 0.8; animation: dash 0.5s linear; animation-iteration-count: infinite; } \ n }
;
2021-05-02 20:24:39 +00:00
my % hef = ( # Energiedaktoren für Verbrauchertypen
"heater" = > { tot = > 1.00 , f = > 0.30 , m = > 0.40 , l = > 0.30 , mt = > 240 } , # tot = Faktor nominaler Gesamtenergieverbrauch
"other" = > { tot = > 1.00 , f = > 0.25 , m = > 0.50 , l = > 0.25 , mt = > $ defmintime } , # !!! Faktoren f,m,l MÜSSEN zusammen 1 ergeben !!!
"dishwasher" = > { tot = > 0.13 , f = > 0.45 , m = > 0.10 , l = > 0.45 , mt = > 180 } , # f = Faktor Energieverbrauch in erster Stunde
"dryer" = > { tot = > 1.00 , f = > 0.40 , m = > 0.40 , l = > 0.20 , mt = > 75 } , # m = Faktor Energieverbrauch zwischen erster und letzter Stunde
"washingmachine" = > { tot = > 0.18 , f = > 0.30 , m = > 0.40 , l = > 0.30 , mt = > 120 } , # l = Faktor Energieverbrauch in letzter Stunde
) ;
2021-03-18 10:31:34 +00:00
# Information zu verwendeten internen Datenhashes
2021-03-23 16:49:43 +00:00
# $data{$type}{$name}{circular} # Ringspeicher
2021-03-18 10:31:34 +00:00
# $data{$type}{$name}{current} # current values
2021-03-23 16:49:43 +00:00
# $data{$type}{$name}{pvhist} # historische Werte
2021-03-20 09:05:26 +00:00
# $data{$type}{$name}{nexthours} # NextHours Werte
2021-05-02 20:24:39 +00:00
# $data{$type}{$name}{consumers} # Consumer Hash
2021-05-30 12:31:18 +00:00
# $data{$type}{$name}{strings} # Stringkonfiguration
2021-03-18 10:31:34 +00:00
2020-12-13 17:29:15 +00:00
################################################################
# Init Fn
################################################################
sub Initialize {
2021-05-02 20:24:39 +00:00
my $ hash = shift ;
2020-12-13 17:29:15 +00:00
2021-03-26 17:50:31 +00:00
my $ fwd = join "," , devspec2array ( "TYPE=FHEMWEB:FILTER=STATE=Initialized" ) ;
2020-12-13 17:29:15 +00:00
2021-05-02 20:24:39 +00:00
my $ consumer ;
for my $ c ( 1 .. $ maxconsumer ) {
$ c = sprintf "%02d" , $ c ;
$ consumer . = "consumer${c}:textField-long " ;
}
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 " .
2021-05-12 13:17:21 +00:00
"beam1Content:pvForecast,pvReal,gridconsumption,consumptionForecast " .
2021-04-03 06:44:58 +00:00
"beam1FontColor:colorpicker,RGB " .
2021-03-14 11:51:38 +00:00
"beam2Color:colorpicker,RGB " .
2021-05-12 13:17:21 +00:00
"beam2Content:pvForecast,pvReal,gridconsumption,consumptionForecast " .
2021-04-03 06:44:58 +00:00
"beam2FontColor:colorpicker,RGB " .
2020-12-13 17:29:15 +00:00
"beamHeight " .
"beamWidth " .
2021-05-29 12:56:28 +00:00
"consumerLegend:none,icon_top,icon_bottom,text_top,text_bottom " .
2021-06-12 12:32:43 +00:00
"consumerAdviceIcon " .
2021-03-25 18:12:26 +00:00
"cloudFactorDamping:slider,0,1,100 " .
2021-06-12 07:03:47 +00:00
"Css:textField-long " .
2021-06-13 09:43:58 +00:00
"debug:1,0 " .
2020-12-13 17:29:15 +00:00
"disable:1,0 " .
2021-05-28 08:33:17 +00:00
"flowGraphicSize " .
"flowGraphicAnimate:1,0 " .
2021-04-27 20:48:54 +00:00
"follow70percentRule:1,dynamic,0 " .
2020-12-13 17:29:15 +00:00
"forcePageRefresh:1,0 " .
2021-06-10 20:32:37 +00:00
"graphicSelect:both,flow,forecast,none " .
2020-12-13 17:29:15 +00:00
"headerDetail:all,co,pv,pvco,statusLink " .
2021-03-26 12:03:05 +00:00
"historyHour:slider,-23,-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 " .
2021-03-25 17:38:19 +00:00
"layoutType:single,double,diff " .
2020-12-27 20:04:17 +00:00
"maxVariancePerDay " .
2021-04-02 06:37:48 +00:00
"maxValBeam " .
2021-03-26 17:50:31 +00:00
"numHistDays:slider,1,1,30 " .
2021-03-25 18:12:26 +00:00
"rainFactorDamping:slider,0,1,100 " .
2021-04-24 07:37:41 +00:00
"sameWeekdaysForConsfc:1,0 " .
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 " .
2021-05-02 20:24:39 +00:00
"weatherColorNight:colorpicker,RGB " .
$ consumer .
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
2021-03-28 08:24:48 +00:00
eval { FHEM::Meta:: InitMod ( __FILE__ , $ hash ) } ; ## no critic 'eval'
2020-12-13 17:29:15 +00:00
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-06-13 19:18:25 +00:00
$ params - > { file } = $ pvhcache . $ name ; # Cache File PV History lesen wenn vorhanden
$ params - > { cachename } = "pvhist" ;
2021-03-23 16:49:43 +00:00
_readCacheFile ( $ params ) ;
2021-06-13 19:18:25 +00:00
$ params - > { file } = $ pvccache . $ name ; # Cache File PV Circular lesen wenn vorhanden
$ params - > { cachename } = "circular" ;
2021-03-23 16:49:43 +00:00
_readCacheFile ( $ params ) ;
2021-06-13 19:18:25 +00:00
$ params - > { file } = $ csmcache . $ name ; # Cache File Consumer lesen wenn vorhanden
$ params - > { cachename } = "consumers" ;
_readCacheFile ( $ params ) ;
2021-03-13 14:48:27 +00:00
2021-06-12 08:37:50 +00:00
readingsSingleUpdate ( $ hash , "state" , "initialized" , 1 ) ;
2021-03-13 14:48:27 +00:00
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-06-13 19:18:25 +00:00
Log3 ( $ name , 3 , qq{ $name - SolarForecast cache "$cachename" restored } ) ;
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 ;
2021-05-30 07:54:58 +00:00
my @ args = @ a ;
2020-12-13 17:29:15 +00:00
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-05-21 09:35:17 +00:00
my $ prop2 = shift @ a ;
2020-12-13 17:29:15 +00:00
2021-05-02 20:24:39 +00:00
return if ( IsDisabled ( $ name ) ) ;
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 ) = ( "" , "" , "" , "" ) ;
2021-05-02 20:24:39 +00:00
2021-05-09 18:29:53 +00:00
my @ re = qw( consumerPlanning
currentBatteryDev
2021-05-02 20:24:39 +00:00
currentForecastDev
currentInverterDev
currentMeterDev
energyH4Trigger
inverterStrings
powerTrigger
pvCorrection
pvHistory
) ;
my $ resets = join "," , @ re ;
2020-12-13 17:29:15 +00:00
@ 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 " .
2021-04-17 07:37:23 +00:00
"currentRadiationDev:$fcd " .
2021-04-09 21:09:20 +00:00
"currentBatteryDev:textField-long " .
2020-12-27 18:51:53 +00:00
"currentInverterDev:textField-long " .
"currentMeterDev:textField-long " .
2021-04-05 14:54:45 +00:00
"energyH4Trigger: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 " .
2021-06-03 16:05:30 +00:00
"plantConfiguration:check,save,restore " .
2021-04-04 06:40:40 +00:00
"powerTrigger:textField-long " .
2020-12-15 13:41:10 +00:00
"pvCorrectionFactor_Auto:on,off " .
2021-05-02 20:24:39 +00:00
"reset:$resets " .
2021-01-23 21:31:11 +00:00
"writeHistory:noArg " .
2020-12-13 17:29:15 +00:00
$ cf
;
my $ params = {
2021-05-30 07:54:58 +00:00
hash = > $ hash ,
name = > $ name ,
opt = > $ opt ,
arg = > $ arg ,
argsref = > \ @ args ,
prop = > $ prop ,
prop1 = > $ prop1 ,
prop2 = > $ prop2
2020-12-13 17:29:15 +00:00
} ;
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 } ;
2021-04-17 07:37:23 +00:00
my $ prop = $ paref - > { prop } // return qq{ no forecast device specified } ;
2020-12-13 17:29:15 +00:00
if ( ! $ defs { $ prop } || $ defs { $ prop } { TYPE } ne "DWD_OpenData" ) {
2021-04-17 07:37:23 +00:00
return qq{ The 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 ) ;
2021-06-03 16:05:30 +00:00
writeDataToFile ( $ hash , "plantconfig" , $ plantcfg . $ name ) ; # Anlagenkonfiguration File schreiben
2020-12-13 17:29:15 +00:00
return ;
}
2021-04-17 07:37:23 +00:00
################################################################
# Setter currentRadiationDev
################################################################
sub _setcurrentRadiationDev { ## no critic "not used"
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ prop = $ paref - > { prop } // return qq{ no radiation device specified } ;
if ( ! $ defs { $ prop } || $ defs { $ prop } { TYPE } ne "DWD_OpenData" ) {
return qq{ The device "$prop" doesn't exist or has no TYPE "DWD_OpenData" } ; #' :)
}
readingsSingleUpdate ( $ hash , "currentRadiationDev" , $ prop , 1 ) ;
createNotifyDev ( $ hash ) ;
2021-06-03 16:05:30 +00:00
writeDataToFile ( $ hash , "plantconfig" , $ plantcfg . $ name ) ; # Anlagenkonfiguration File schreiben
2021-04-17 07:37:23 +00:00
return ;
}
2020-12-13 17:29:15 +00:00
################################################################
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-04-04 06:40:40 +00:00
return qq{ The syntax of "$opt" is not correct. Please consider the commandref. } ;
2021-01-21 18:23:21 +00:00
}
2020-12-13 17:29:15 +00:00
2020-12-15 13:41:10 +00:00
readingsSingleUpdate ( $ hash , "currentInverterDev" , $ arg , 1 ) ;
2020-12-13 17:29:15 +00:00
createNotifyDev ( $ hash ) ;
2021-06-03 16:05:30 +00:00
writeDataToFile ( $ hash , "plantconfig" , $ plantcfg . $ name ) ; # Anlagenkonfiguration File schreiben
2020-12-13 17:29:15 +00:00
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 } ;
2021-06-03 16:05:30 +00:00
readingsSingleUpdate ( $ hash , "inverterStrings" , $ prop , 1 ) ;
writeDataToFile ( $ hash , "plantconfig" , $ plantcfg . $ name ) ; # Anlagenkonfiguration File schreiben
2021-01-26 20:38:22 +00:00
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-04-06 15:50:16 +00:00
if ( ! $ h - > { gcon } || ! $ h - > { contotal } || ! $ h - > { gfeedin } || ! $ h - > { feedtotal } ) {
2021-04-04 06:40:40 +00:00
return qq{ The syntax of "$opt" is not correct. Please consider the commandref. } ;
2021-04-09 16:11:17 +00:00
}
if ( $ h - > { gcon } eq "-gfeedin" && $ h - > { gfeedin } eq "-gcon" ) {
return qq{ Incorrect input. It is not allowed that the keys gcon and gfeedin refer to each other. } ;
2021-01-21 18:23:21 +00:00
}
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 ) ;
2021-06-03 16:05:30 +00:00
writeDataToFile ( $ hash , "plantconfig" , $ plantcfg . $ name ) ; # Anlagenkonfiguration File schreiben
2020-12-13 17:29:15 +00:00
return ;
}
2021-04-09 21:09:20 +00:00
################################################################
# Setter currentBatteryDev
################################################################
sub _setbatteryDevice { ## no critic "not used"
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ opt = $ paref - > { opt } ;
my $ arg = $ paref - > { arg } ;
if ( ! $ arg ) {
return qq{ The command "$opt" needs an argument ! } ;
}
my ( $ a , $ h ) = parseParams ( $ arg ) ;
my $ badev = $ a - > [ 0 ] // "" ;
if ( ! $ badev || ! $ defs { $ badev } ) {
return qq{ The device "$badev" doesn't exist! } ;
}
if ( ! $ h - > { pin } || ! $ h - > { pout } ) {
return qq{ The syntax of "$opt" is not correct. Please consider the commandref. } ;
}
if ( $ h - > { pin } eq "-pout" && $ h - > { pout } eq "-pin" ) {
return qq{ Incorrect input. It is not allowed that the keys pin and pout refer to each other. } ;
}
readingsSingleUpdate ( $ hash , "currentBatteryDev" , $ arg , 1 ) ;
createNotifyDev ( $ hash ) ;
2021-06-03 16:05:30 +00:00
writeDataToFile ( $ hash , "plantconfig" , $ plantcfg . $ name ) ; # Anlagenkonfiguration File schreiben
2021-04-09 21:09:20 +00:00
return ;
}
2021-04-04 06:40:40 +00:00
################################################################
# Setter powerTrigger
################################################################
sub _setpowerTrigger { ## no critic "not used"
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ opt = $ paref - > { opt } ;
my $ arg = $ paref - > { arg } ;
if ( ! $ arg ) {
return qq{ The command "$opt" needs an argument ! } ;
}
my ( $ a , $ h ) = parseParams ( $ arg ) ;
if ( ! $ h ) {
return qq{ The syntax of "$opt" is not correct. Please consider the commandref. } ;
}
for my $ key ( keys % { $ h } ) {
2021-04-04 07:45:35 +00:00
if ( $ key !~ /^[0-9]+(?:on|off)$/x || $ h - > { $ key } !~ /^[0-9]+$/x ) {
2021-04-04 06:40:40 +00:00
return qq{ The key "$key" is invalid. Please consider the commandref. } ;
}
}
2021-06-03 16:05:30 +00:00
writeDataToFile ( $ hash , "plantconfig" , $ plantcfg . $ name ) ; # Anlagenkonfiguration File schreiben
2021-04-04 06:40:40 +00:00
readingsSingleUpdate ( $ hash , "powerTrigger" , $ arg , 1 ) ;
return ;
}
2021-04-05 14:54:45 +00:00
################################################################
# Setter energyH4Trigger
################################################################
sub _setenergyH4Trigger { ## no critic "not used"
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ opt = $ paref - > { opt } ;
my $ arg = $ paref - > { arg } ;
if ( ! $ arg ) {
return qq{ The command "$opt" needs an argument ! } ;
}
my ( $ a , $ h ) = parseParams ( $ arg ) ;
if ( ! $ h ) {
return qq{ The syntax of "$opt" is not correct. Please consider the commandref. } ;
}
for my $ key ( keys % { $ h } ) {
if ( $ key !~ /^[0-9]+(?:on|off)$/x || $ h - > { $ key } !~ /^[0-9]+$/x ) {
return qq{ The key "$key" is invalid. Please consider the commandref. } ;
}
}
2021-06-03 16:05:30 +00:00
writeDataToFile ( $ hash , "plantconfig" , $ plantcfg . $ name ) ; # Anlagenkonfiguration File schreiben
2021-04-05 14:54:45 +00:00
readingsSingleUpdate ( $ hash , "energyH4Trigger" , $ arg , 1 ) ;
return ;
}
2020-12-13 17:29:15 +00:00
################################################################
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 ) ;
2021-06-03 16:05:30 +00:00
writeDataToFile ( $ hash , "plantconfig" , $ plantcfg . $ name ) ; # Anlagenkonfiguration File schreiben
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 ) {
2021-04-02 12:20:29 +00:00
if ( $ value !~ /^(?:$tilt)$/x ) {
2021-01-26 20:38:22 +00:00
return qq{ The tilt angle of "$key" is wrong } ;
}
}
readingsSingleUpdate ( $ hash , "moduleTiltAngle" , $ arg , 1 ) ;
my $ ret = createStringConfig ( $ hash ) ;
2021-06-03 16:05:30 +00:00
return $ ret if ( $ ret ) ;
writeDataToFile ( $ hash , "plantconfig" , $ plantcfg . $ name ) ; # Anlagenkonfiguration File schreiben
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 ) {
2021-04-02 12:20:29 +00:00
if ( $ value !~ /^(?:$dirs)$/x ) {
2021-01-26 20:38:22 +00:00
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-06-03 16:05:30 +00:00
2021-06-10 20:32:37 +00:00
writeDataToFile ( $ hash , "plantconfig" , $ plantcfg . $ name ) ; # Anlagenkonfiguration File schreiben
2021-01-24 18:03:44 +00:00
return ;
}
2021-06-03 16:05:30 +00:00
################################################################
# Setter plantConfiguration
################################################################
sub _setplantConfiguration { ## no critic "not used"
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ opt = $ paref - > { opt } ;
my $ arg = $ paref - > { arg } ;
if ( ! $ arg ) {
return qq{ The command "$opt" needs an argument ! } ;
}
if ( $ arg eq "check" ) {
my $ ret = checkStringConfig ( $ hash ) ;
return qq{ <html>$ret</html> } ;
}
if ( $ arg eq "save" ) {
my $ error = writeDataToFile ( $ hash , "plantconfig" , $ plantcfg . $ name ) ; # Anlagenkonfiguration File schreiben
if ( $ error ) {
return $ error ;
}
else {
return qq{ Plant Configuration has been written to file "$plantcfg.$name" } ;
}
}
if ( $ arg eq "restore" ) {
my ( $ error , @ pvconf ) = FileRead ( $ plantcfg . $ name ) ;
if ( ! $ error ) {
my $ rbit = 0 ;
for my $ elem ( @ pvconf ) {
my ( $ reading , $ val ) = split "<>" , $ elem ;
next if ( ! $ reading || ! defined $ val ) ;
CommandSetReading ( undef , "$name $reading $val" ) ;
$ rbit = 1 ;
}
if ( $ rbit ) {
return qq{ Plant Configuration restored from file "$plantcfg.$name" } ;
}
else {
return qq{ The Plant Configuration file "$plantcfg.$name" was empty, nothing restored } ;
}
}
else {
return $ error ;
}
}
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
2021-04-03 10:33:26 +00:00
my $ cfnum = ( split "_" , $ opt ) [ 1 ] ;
deleteReadingspec ( $ hash , "pvCorrectionFactor_${cfnum}_autocalc" ) ;
2021-05-02 20:24:39 +00:00
centralTask ( $ hash ) ;
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 } ;
2021-06-10 20:32:37 +00:00
my $ name = $ paref - > { name } ;
2020-12-15 13:41:10 +00:00
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" ) ;
}
2021-06-10 20:32:37 +00:00
writeDataToFile ( $ hash , "plantconfig" , $ plantcfg . $ name ) ; # Anlagenkonfiguration sichern
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 } ;
2021-04-09 21:22:48 +00:00
my $ type = $ hash - > { TYPE } ;
2021-01-26 20:38:22 +00:00
if ( $ prop eq "pvHistory" ) {
2021-05-21 09:35:17 +00:00
my $ day = $ paref - > { prop1 } // "" ; # ein bestimmter Tag der pvHistory angegeben ?
my $ dhour = $ paref - > { prop2 } // "" ; # eine bestimmte Stunde eines Tages der pvHistory angegeben ?
2021-05-09 18:29:53 +00:00
if ( $ day ) {
2021-05-21 09:35:17 +00:00
if ( $ dhour ) {
delete $ data { $ type } { $ name } { pvhist } { $ day } { $ dhour } ;
Log3 ( $ name , 3 , qq{ $name - Hour "$dhour" of day "$day" deleted in pvHistory } ) ;
}
else {
delete $ data { $ type } { $ name } { pvhist } { $ day } ;
Log3 ( $ name , 3 , qq{ $name - Day "$day" deleted in pvHistory } ) ;
}
2021-05-09 18:29:53 +00:00
}
else {
delete $ data { $ type } { $ name } { pvhist } ;
Log3 ( $ name , 3 , qq{ $name - all days of pvHistory deleted } ) ;
}
2021-01-26 20:38:22 +00:00
return ;
}
if ( $ prop eq "pvCorrection" ) {
2021-04-05 14:54:45 +00:00
deleteReadingspec ( $ hash , "pvCorrectionFactor_.*" ) ;
2021-01-26 20:38:22 +00:00
return ;
}
2021-04-04 06:40:40 +00:00
if ( $ prop eq "powerTrigger" ) {
2021-04-05 14:54:45 +00:00
deleteReadingspec ( $ hash , "powerTrigger.*" ) ;
2021-06-03 16:05:30 +00:00
writeDataToFile ( $ hash , "plantconfig" , $ plantcfg . $ name ) ; # Anlagenkonfiguration File schreiben
2021-04-05 14:54:45 +00:00
return ;
}
if ( $ prop eq "energyH4Trigger" ) {
deleteReadingspec ( $ hash , "energyH4Trigger.*" ) ;
2021-06-03 16:05:30 +00:00
writeDataToFile ( $ hash , "plantconfig" , $ plantcfg . $ name ) ; # Anlagenkonfiguration File schreiben
2021-04-04 06:40:40 +00:00
return ;
}
2021-01-26 20:38:22 +00:00
readingsDelete ( $ hash , $ prop ) ;
if ( $ prop eq "currentMeterDev" ) {
readingsDelete ( $ hash , "Current_GridConsumption" ) ;
2021-04-09 21:09:20 +00:00
readingsDelete ( $ hash , "Current_GridFeedIn" ) ;
delete $ hash - > { HELPER } { INITCONTOTAL } ;
delete $ hash - > { HELPER } { INITFEEDTOTAL } ;
2021-04-09 21:22:48 +00:00
delete $ data { $ type } { $ name } { current } { gridconsumption } ;
2021-04-17 07:52:06 +00:00
delete $ data { $ type } { $ name } { current } { tomorrowconsumption } ;
2021-04-09 21:22:48 +00:00
delete $ data { $ type } { $ name } { current } { gridfeedin } ;
delete $ data { $ type } { $ name } { current } { consumption } ;
2021-05-11 16:42:32 +00:00
delete $ data { $ type } { $ name } { current } { autarkyrate } ;
delete $ data { $ type } { $ name } { current } { selfconsumption } ;
delete $ data { $ type } { $ name } { current } { selfconsumptionrate } ;
2021-06-03 16:05:30 +00:00
2021-06-13 19:18:25 +00:00
writeDataToFile ( $ hash , "plantconfig" , $ plantcfg . $ name ) ; # Anlagenkonfiguration File schreiben
2021-04-09 21:22:48 +00:00
}
if ( $ prop eq "currentBatteryDev" ) {
readingsDelete ( $ hash , "Current_PowerBatIn" ) ;
readingsDelete ( $ hash , "Current_PowerBatOut" ) ;
2021-05-27 20:35:27 +00:00
readingsDelete ( $ hash , "Current_BatCharge" ) ;
2021-04-09 21:22:48 +00:00
delete $ data { $ type } { $ name } { current } { powerbatout } ;
delete $ data { $ type } { $ name } { current } { powerbatin } ;
2021-05-27 20:35:27 +00:00
delete $ data { $ type } { $ name } { current } { batcharge } ;
2021-06-03 16:05:30 +00:00
2021-06-13 19:18:25 +00:00
writeDataToFile ( $ hash , "plantconfig" , $ plantcfg . $ name ) ; # Anlagenkonfiguration File schreiben
2021-01-26 20:38:22 +00:00
}
if ( $ prop eq "currentInverterDev" ) {
readingsDelete ( $ hash , "Current_PV" ) ;
deleteReadingspec ( $ hash , ".*_PVreal" ) ;
2021-06-13 19:18:25 +00:00
writeDataToFile ( $ hash , "plantconfig" , $ plantcfg . $ name ) ; # Anlagenkonfiguration File schreiben
2021-01-26 20:38:22 +00:00
}
2021-06-13 19:18:25 +00:00
if ( $ prop eq "consumerPlanning" ) { # Verbraucherplanung resetten
my $ c = $ paref - > { prop1 } // "" ; # bestimmten Verbraucher setzen falls angegeben
2021-05-09 18:29:53 +00:00
if ( $ c ) {
deleteConsumerPlanning ( $ hash , $ c ) ;
my $ calias = ConsumerVal ( $ hash , $ c , "alias" , "" ) ;
Log3 ( $ name , 3 , qq{ $name - Consumer planning of "$calias" deleted } ) ;
}
else {
for my $ cs ( keys % { $ data { $ type } { $ name } { consumers } } ) {
deleteConsumerPlanning ( $ hash , $ cs ) ;
my $ calias = ConsumerVal ( $ hash , $ cs , "alias" , "" ) ;
Log3 ( $ name , 3 , qq{ $name - Consumer planning of "$calias" deleted } ) ;
}
}
2021-06-13 19:18:25 +00:00
writeDataToFile ( $ hash , "consumers" , $ csmcache . $ name ) ; # Cache File Consumer schreiben
2021-05-09 18:29:53 +00:00
}
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 } ;
2021-04-10 11:55:03 +00:00
my $ ret ;
2021-03-13 14:48:27 +00:00
2021-06-03 16:05:30 +00:00
$ ret = writeDataToFile ( $ hash , "circular" , $ pvccache . $ name ) ; # Cache File für PV Circular schreiben
$ ret = writeDataToFile ( $ hash , "pvhist" , $ pvhcache . $ name ) ; # Cache File für PV History schreiben
2021-01-23 21:31:11 +00:00
return $ ret ;
}
2021-05-30 07:54:58 +00:00
################################################################
# Setter consumerAction
# ohne Menüeintrag ! für Aktivität aus Grafik
################################################################
sub _setconsumerAction { ## no critic "not used"
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ opt = $ paref - > { opt } ;
my $ arg = $ paref - > { arg } ;
my $ argsref = $ paref - > { argsref } ;
if ( ! $ arg ) {
return qq{ The command "$opt" needs an argument ! } ;
}
my @ args = @ { $ argsref } ;
my $ action = shift @ args ; # z.B. set, setreading
my $ cname = shift @ args ; # Consumername
my $ tail = join " " , map { my $ p = $ _ ; $ p =~ s/\s//xg ; $ p ; } @ args ; # restliche Befehlsargumente ## no critic 'Map blocks'
if ( $ action eq "set" ) {
CommandSet ( undef , "$cname $tail" ) ;
}
if ( $ action eq "setreading" ) {
CommandSetReading ( undef , "$cname $tail" ) ;
}
Log3 ( $ name , 4 , qq{ $name - Consumer Action received / executed: "$action $cname $tail" } ) ;
centralTask ( $ hash ) ;
# RemoveInternalTimer($hash, "FHEM::SolarForecast::centralTask");
# InternalTimer (gettimeofday()+0.5, "FHEM::SolarForecast::centralTask", $hash, 0);
return ;
}
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 " .
2021-05-02 20:24:39 +00:00
"valConsumerMaster:noArg " .
2021-01-17 19:13:02 +00:00
"data:noArg " .
2021-04-17 07:37:23 +00:00
"forecastQualities:noArg " .
2021-01-17 19:13:02 +00:00
"html:noArg " .
2021-03-20 09:05:26 +00:00
"nextHours:noArg " .
2021-03-23 16:49:43 +00:00
"pvCircular:noArg " .
2021-01-26 20:38:22 +00:00
"pvHistory:noArg " .
2021-03-28 08:24:48 +00:00
"valCurrent: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" ) ;
}
###############################################################
2021-04-17 07:37:23 +00:00
# Getter pvHistory
2021-01-17 19:13:02 +00:00
###############################################################
sub _getlistPVHistory {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
2021-03-17 20:06:19 +00:00
my $ ret = listDataPool ( $ hash , "pvhist" ) ;
return $ ret ;
}
###############################################################
2021-03-23 16:49:43 +00:00
# Getter pvCircular
2021-03-18 17:25:37 +00:00
###############################################################
2021-03-23 16:49:43 +00:00
sub _getlistPVCircular {
2021-03-18 17:25:37 +00:00
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
2021-03-23 16:49:43 +00:00
my $ ret = listDataPool ( $ hash , "circular" ) ;
2021-03-18 17:25:37 +00:00
return $ ret ;
}
2021-03-20 09:05:26 +00:00
###############################################################
2021-04-17 07:37:23 +00:00
# Getter nextHours
2021-03-20 09:05:26 +00:00
###############################################################
sub _getlistNextHours {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ ret = listDataPool ( $ hash , "nexthours" ) ;
return $ ret ;
}
2021-04-17 07:37:23 +00:00
###############################################################
# Getter pvQualities
###############################################################
sub _getForecastQualities {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ ret = listDataPool ( $ hash , "qualities" ) ;
return $ ret ;
}
2021-03-28 08:24:48 +00:00
###############################################################
# Getter valCurrent
###############################################################
sub _getlistCurrent {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ ret = listDataPool ( $ hash , "current" ) ;
return $ ret ;
}
2021-05-02 20:24:39 +00:00
###############################################################
# Getter valConsumerMaster
###############################################################
sub _getlistvalConsumerMaster {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ ret = listDataPool ( $ hash , "consumer" ) ;
return $ ret ;
}
2020-12-13 17:29:15 +00:00
################################################################
sub Attr {
2021-05-02 20:24:39 +00:00
my $ cmd = shift ;
my $ name = shift ;
my $ aName = shift ;
my $ aVal = shift ;
my $ hash = $ defs { $ name } ;
2021-04-24 07:37:41 +00:00
2021-05-02 20:24:39 +00:00
my ( $ do , $ val ) ;
2020-12-13 17:29:15 +00:00
2021-05-02 20:24:39 +00:00
# $cmd can be "del" or "set"
# $name is device name
# aName and aVal are Attribute name and value
2020-12-13 17:29:15 +00:00
2021-05-02 20:24:39 +00:00
if ( $ aName eq "disable" ) {
if ( $ cmd eq "set" ) {
$ do = ( $ aVal ) ? 1 : 0 ;
}
$ do = 0 if ( $ cmd eq "del" ) ;
$ val = ( $ do == 1 ? "disabled" : "initialized" ) ;
readingsSingleUpdate ( $ hash , "state" , $ val , 1 ) ;
}
2020-12-13 17:29:15 +00:00
2021-05-02 20:24:39 +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
2021-05-02 20:24:39 +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 !" ; }
}
}
my $ params = {
hash = > $ hash ,
name = > $ name ,
cmd = > $ cmd ,
aName = > $ aName ,
aVal = > $ aVal
} ;
2021-05-12 11:12:59 +00:00
2021-05-29 12:56:28 +00:00
$ aName = "consumer" if ( $ aName =~ /consumer?(\d+)$/xs ) ;
2021-05-02 20:24:39 +00:00
if ( $ hattr { $ aName } && defined & { $ hattr { $ aName } { fn } } ) {
my $ ret = q{ } ;
$ ret = & { $ hattr { $ aName } { fn } } ( $ params ) ;
return $ ret ;
}
return ;
}
################################################################
# Setter consumer
################################################################
sub _attrconsumer { ## no critic "not used"
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ aName = $ paref - > { aName } ;
my $ aVal = $ paref - > { aVal } ;
my $ cmd = $ paref - > { cmd } ;
2021-05-30 19:20:33 +00:00
return if ( ! $ init_done ) ; # Forum: https://forum.fhem.de/index.php/topic,117864.msg1159959.html#msg1159959
2021-05-30 18:37:08 +00:00
2021-05-02 20:24:39 +00:00
if ( $ cmd eq "set" ) {
my ( $ a , $ h ) = parseParams ( $ aVal ) ;
my $ codev = $ a - > [ 0 ] // "" ;
if ( ! $ codev || ! $ defs { $ codev } ) {
return qq{ The device "$codev" doesn't exist! } ;
}
if ( ! $ h - > { type } || ! $ h - > { power } ) {
return qq{ The syntax of "$aName" is not correct. Please consider the commandref. } ;
}
my $ alowt = $ h - > { type } ~ ~ @ ctypes ? 1 : 0 ;
if ( ! $ alowt ) {
return qq{ The type "$h-> { type } " isn't allowed! } ;
}
if ( $ h - > { power } !~ /^[0-9]+$/xs ) {
return qq{ The key "power" must be specified only by numbers without decimal places } ;
}
if ( $ h - > { mode } && $ h - > { mode } !~ /^(?:can|must)$/xs ) {
return qq{ The mode "$h-> { mode } " isn't allowed! }
}
}
2021-05-09 18:29:53 +00:00
else {
2021-05-02 20:24:39 +00:00
my $ day = strftime "%d" , localtime ( time ) ; # aktueller Tag (range 01 to 31)
my $ type = $ hash - > { TYPE } ;
2021-05-17 18:39:29 +00:00
my ( $ co ) = $ aName =~ /consumer([0-9]+)/xs ;
2021-05-02 20:24:39 +00:00
2021-05-17 18:39:29 +00:00
deleteReadingspec ( $ hash , "consumer${co}.*" ) ;
2021-05-09 18:29:53 +00:00
2021-05-02 20:24:39 +00:00
for my $ i ( 1 .. 24 ) { # Consumer aus History löschen
2021-05-17 18:39:29 +00:00
delete $ data { $ type } { $ name } { pvhist } { $ day } { sprintf ( "%02d" , $ i ) } { "csmt${co}" } ;
delete $ data { $ type } { $ name } { pvhist } { $ day } { sprintf ( "%02d" , $ i ) } { "csme${co}" } ;
2021-05-02 20:24:39 +00:00
}
2021-05-17 18:39:29 +00:00
delete $ data { $ type } { $ name } { pvhist } { $ day } { 99 } { "csmt${co}" } ;
delete $ data { $ type } { $ name } { pvhist } { $ day } { 99 } { "csme${co}" } ;
delete $ data { $ type } { $ name } { consumers } { $ co } ; # Consumer Hash Verbraucher löschen
2021-05-02 20:24:39 +00:00
}
2021-06-13 19:18:25 +00:00
writeDataToFile ( $ hash , "consumers" , $ csmcache . $ name ) ; # Cache File Consumer schreiben
2021-05-02 20:24:39 +00:00
InternalTimer ( gettimeofday ( ) + 5 , "FHEM::SolarForecast::createNotifyDev" , $ hash , 0 ) ;
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
2021-05-30 07:54:58 +00:00
return ; # !! ZUR ZEIT NICHT GENUTZT !!
2020-12-13 17:29:15 +00:00
return if ( IsDisabled ( $ myName ) || ! $ myHash - > { NOTIFYDEV } ) ;
2021-05-30 07:54:58 +00:00
my $ events = deviceEvents ( $ dev_hash , 1 ) ;
2020-12-13 17:29:15 +00:00
return if ( ! $ events ) ;
2021-05-30 07:54:58 +00:00
my $ cdref = CurrentVal ( $ myHash , "consumerdevs" , "" ) ; # alle registrierten Consumer
my @ consumers = ( ) ;
@ consumers = @ { $ cdref } if ( ref $ cdref eq "ARRAY" ) ;
return if ( ! @ consumers ) ;
if ( $ devName ~ ~ @ consumers ) {
my $ cindex ;
my $ type = $ myHash - > { TYPE } ;
for my $ c ( sort { $ a <=> $ b } keys % { $ data { $ type } { $ myName } { consumers } } ) {
my $ cname = ConsumerVal ( $ myHash , $ c , "name" , "" ) ;
if ( $ devName eq $ cname ) {
$ cindex = $ c ;
last ;
}
}
my $ autoreading = ConsumerVal ( $ myHash , $ cindex , "autoreading" , "" ) ;
for my $ event ( @ { $ events } ) {
$ event = "" if ( ! defined ( $ event ) ) ;
my @ evl = split ( /\s+/x , $ event ) ;
my @ parts = split ( /: / , $ event , 2 ) ;
my $ reading = shift @ parts ;
if ( $ reading eq "state" || $ reading eq $ autoreading ) {
Log3 ( $ myName , 4 , qq{ $myName - start centralTask by Notify - $devName:$reading } ) ;
RemoveInternalTimer ( $ myHash , "FHEM::SolarForecast::centralTask" ) ;
InternalTimer ( gettimeofday ( ) + 0.5 , "FHEM::SolarForecast::centralTask" , $ myHash , 0 ) ;
}
}
}
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 ) = ( "" , "" , "" ) ;
2021-04-26 20:16:40 +00:00
if ( $ event =~ /\s(k?Wh?|%)$/xs ) {
2021-03-01 21:18:23 +00:00
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-06-13 19:18:25 +00:00
writeDataToFile ( $ hash , "pvhist" , $ pvhcache . $ name ) ; # Cache File für PV History schreiben
writeDataToFile ( $ hash , "circular" , $ pvccache . $ name ) ; # Cache File für PV Circular schreiben
writeDataToFile ( $ hash , "consumers" , $ csmcache . $ name ) ; # Cache File Consumer 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-06-03 16:05:30 +00:00
my $ file = $ pvhcache . $ name ; # Cache File PV History löschen
2021-03-13 15:50:33 +00:00
my $ error = FileDelete ( $ file ) ;
if ( $ error ) {
2021-06-03 16:05:30 +00:00
Log3 ( $ name , 1 , qq{ $name - ERROR deleting file "$file": $error } ) ;
}
$ error = qq{ } ;
$ file = $ pvccache . $ name ; # Cache File PV Circular löschen
$ error = FileDelete ( $ file ) ;
if ( $ error ) {
Log3 ( $ name , 1 , qq{ $name - ERROR deleting file "$file": $error } ) ;
2021-03-23 16:49:43 +00:00
}
2021-03-13 15:50:33 +00:00
2021-06-03 16:05:30 +00:00
2021-03-13 15:50:33 +00:00
$ error = qq{ } ;
2021-06-03 16:05:30 +00:00
$ file = $ plantcfg . $ name ; # File Anlagenkonfiguration löschen
2021-03-13 15:50:33 +00:00
$ error = FileDelete ( $ file ) ;
2021-01-17 19:13:02 +00:00
if ( $ error ) {
2021-06-03 16:05:30 +00:00
Log3 ( $ name , 1 , qq{ $name - ERROR deleting file "$file": $error } ) ;
2021-01-17 19:13:02 +00:00
}
2021-06-13 19:18:25 +00:00
$ error = qq{ } ;
$ file = $ csmcache . $ name ; # File Consumer löschen
$ error = FileDelete ( $ file ) ;
if ( $ error ) {
Log3 ( $ name , 1 , qq{ $name - ERROR deleting file "$file": $error } ) ;
}
2021-01-17 19:13:02 +00:00
return ;
}
2021-06-03 16:05:30 +00:00
################################################################
# Timer für historische Daten schreiben
################################################################
sub periodicWriteCachefiles {
my $ hash = shift ;
my $ name = $ hash - > { NAME } ;
RemoveInternalTimer ( $ hash , "FHEM::SolarForecast::periodicWriteCachefiles" ) ;
InternalTimer ( gettimeofday ( ) + $ whistrepeat , "FHEM::SolarForecast::periodicWriteCachefiles" , $ hash , 0 ) ;
return if ( IsDisabled ( $ name ) ) ;
2021-06-13 19:18:25 +00:00
writeDataToFile ( $ hash , "circular" , $ pvccache . $ name ) ; # Cache File für PV Circular schreiben
writeDataToFile ( $ hash , "pvhist" , $ pvhcache . $ name ) ; # Cache File für PV History schreiben
2021-06-03 16:05:30 +00:00
return ;
}
################################################################
# Daten in File wegschreiben
################################################################
sub writeDataToFile {
my $ hash = shift ;
my $ cachename = shift ;
my $ file = shift ;
my $ name = $ hash - > { NAME } ;
my $ type = $ hash - > { TYPE } ;
my @ data ;
if ( $ cachename eq "plantconfig" ) {
@ data = _savePlantConfig ( $ hash ) ;
return "Plant configuration is empty, no data has been written" if ( ! @ data ) ;
}
else {
return if ( ! $ data { $ type } { $ name } { $ cachename } ) ;
my $ json = encode_json ( $ data { $ type } { $ name } { $ cachename } ) ;
push @ data , $ json ;
}
my $ error = FileWrite ( $ file , @ data ) ;
if ( $ error ) {
my $ err = qq{ ERROR writing cache file "$file": $error } ;
Log3 ( $ name , 1 , "$name - $err" ) ;
readingsSingleUpdate ( $ hash , "state" , "ERROR writing cache file $file - $error" , 1 ) ;
return $ err ;
}
else {
my $ lw = gettimeofday ( ) ;
$ hash - > { HISTFILE } = "last write time: " . FmtTime ( $ lw ) . " File: $file" if ( $ cachename eq "pvhist" ) ;
readingsSingleUpdate ( $ hash , "state" , "wrote cachefile $cachename successfully" , 1 ) ;
}
return ;
}
################################################################
# Anlagenkonfiguration sichern
################################################################
sub _savePlantConfig {
my $ hash = shift ;
my $ name = $ hash - > { NAME } ;
my @ pvconf ;
my @ aconfigs = qw(
2021-06-10 20:32:37 +00:00
pvCorrectionFactor_Auto
2021-06-03 16:05:30 +00:00
currentBatteryDev
currentForecastDev
currentInverterDev
currentMeterDev
currentRadiationDev
inverterStrings
moduleDirection
modulePeakString
moduleTiltAngle
powerTrigger
energyH4Trigger
) ;
for my $ cfg ( @ aconfigs ) {
my $ val = ReadingsVal ( $ name , $ cfg , "" ) ;
next if ( ! $ val ) ;
push @ pvconf , $ cfg . "<>" . $ val ;
}
return @ pvconf ;
}
2020-12-15 13:41:10 +00:00
################################################################
# Zentraler Datenabruf
################################################################
sub centralTask {
my $ hash = shift ;
2021-04-17 07:37:23 +00:00
my $ name = $ hash - > { NAME } ;
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 !!
2021-05-11 16:42:32 +00:00
#for my $i (keys %{$data{$type}{$name}{pvhist}}) {
2021-04-17 07:37:23 +00:00
# delete $data{$type}{$name}{pvhist}{$i}{"00"};
2021-05-11 16:42:32 +00:00
# delete $data{$type}{$name}{pvhist}{$i} if(!$i); # evtl. vorhandene leere Schlüssel entfernen
#}
2021-04-17 07:37:23 +00:00
#deleteReadingspec ($hash, "Today_Hour.*_Consumption");
#deleteReadingspec ($hash, "ThisHour_.*");
#deleteReadingspec ($hash, "Today_PV");
#deleteReadingspec ($hash, "Tomorrow_PV");
#deleteReadingspec ($hash, "Next04Hours_PV");
#deleteReadingspec ($hash, "Next.*HoursPVforecast");
#deleteReadingspec ($hash, "moduleEfficiency");
#deleteReadingspec ($hash, "RestOfDay_PV");
#deleteReadingspec ($hash, "CurrentHourPVforecast");
2021-06-10 20:32:37 +00:00
#deleteReadingspec ($hash, "NextHours_Sum00_PVforecast");
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" ;
2021-04-14 12:18:26 +00:00
readingsSingleUpdate ( $ hash , "nextPolltime" , "Manual" , 1 ) ;
2020-12-15 13:41:10 +00:00
}
else {
my $ new = gettimeofday ( ) + $ interval ;
InternalTimer ( $ new , "FHEM::SolarForecast::centralTask" , $ hash , 0 ) ; # Wiederholungsintervall
$ hash - > { MODE } = "Automatic - next polltime: " . FmtTime ( $ new ) ;
2021-04-14 12:18:26 +00:00
readingsSingleUpdate ( $ hash , "nextPolltime" , FmtTime ( $ new ) , 1 ) ;
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 ;
2021-04-14 17:12:33 +00:00
my $ t = time ; # aktuelle Unix-Zeit
2021-04-21 10:53:14 +00:00
my $ chour = strftime "%H" , localtime ( $ t ) ; # aktuelle Stunde
2021-04-14 17:12:33 +00:00
my $ minute = strftime "%M" , localtime ( $ t ) ; # aktuelle Minute
2021-04-21 10:53:14 +00:00
my $ day = strftime "%d" , localtime ( $ t ) ; # aktueller Tag (range 01 to 31)
2021-04-14 17:12:33 +00:00
my $ dayname = strftime "%a" , localtime ( $ t ) ; # aktueller Wochentagsname
2020-12-15 13:41:10 +00:00
my $ params = {
2021-04-14 17:12:33 +00:00
hash = > $ hash ,
name = > $ name ,
t = > $ t ,
minute = > $ minute ,
chour = > $ chour ,
day = > $ day ,
dayname = > $ dayname ,
state = > "updated" ,
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-05-02 20:24:39 +00:00
collectAllRegConsumers ( $ params ) ; # alle Verbraucher Infos laden
2021-04-04 06:40:40 +00:00
_additionalActivities ( $ params ) ; # zusätzliche Events generieren + Sonderaufgaben
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
2021-04-04 06:40:40 +00:00
_transferMeterValues ( $ params ) ; # Energy Meter auswerten
2021-05-02 20:24:39 +00:00
_transferBatteryValues ( $ params ) ; # Batteriewerte einsammeln
_manageConsumerData ( $ params ) ; # Consumerdaten sammeln und planen
2021-04-24 07:37:41 +00:00
_estConsumptionForecast ( $ params ) ; # erwarteten Verbrauch berechnen
_evaluateThresholds ( $ params ) ; # Schwellenwerte bewerten und signalisieren
2021-04-05 08:29:56 +00:00
_calcSummaries ( $ params ) ; # Zusammenfassungen erstellen
2021-03-24 09:07:17 +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-04-17 07:37:23 +00:00
calcVariance ( $ params ) ; # Autokorrektur berechnen
saveEnergyConsumption ( $ params ) ; # Energie Hausverbrauch speichern
2021-03-24 09:07:17 +00:00
2021-04-09 21:09:20 +00:00
readingsSingleUpdate ( $ hash , "state" , $ params - > { state } , 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
}
2021-05-17 18:39:29 +00:00
my @ sca = keys % { $ data { $ type } { $ name } { strings } } ; # Gegencheck ob nicht mehr Strings in inverterStrings enthalten sind als eigentlich verwendet
2021-01-26 20:38:22 +00:00
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-03-24 15:38:43 +00:00
################################################################
2021-04-02 19:58:48 +00:00
# Zusätzliche Readings/ Events für Logging generieren und
2021-03-24 15:38:43 +00:00
# Sonderaufgaben !
################################################################
2021-04-04 06:40:40 +00:00
sub _additionalActivities {
2021-03-24 15:38:43 +00:00
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ chour = $ paref - > { chour } ;
2021-04-02 19:58:48 +00:00
my $ daref = $ paref - > { daref } ;
2021-04-12 20:08:26 +00:00
my $ t = $ paref - > { t } ; # Epoche Zeit
2021-05-02 20:24:39 +00:00
my $ day = $ paref - > { day } ;
2021-03-24 15:38:43 +00:00
2021-04-12 20:08:26 +00:00
my $ type = $ hash - > { TYPE } ;
my $ date = strftime "%Y-%m-%d" , localtime ( $ t ) ; # aktuelles Datum
2021-03-24 15:38:43 +00:00
2021-04-02 19:58:48 +00:00
my ( $ ts , $ ts1 , $ pvfc , $ pvrl , $ gcon ) ;
2021-03-24 15:38:43 +00:00
2021-04-04 07:45:35 +00:00
$ ts1 = $ date . " " . sprintf ( "%02d" , $ chour ) . ":00:00" ;
2021-03-24 15:38:43 +00:00
2021-04-02 19:58:48 +00:00
$ pvfc = ReadingsNum ( $ name , "Today_Hour" . sprintf ( "%02d" , $ chour ) . "_PVforecast" , 0 ) ;
2021-04-04 07:45:35 +00:00
push @$ daref , "LastHourPVforecast<>" . $ pvfc . " Wh<>" . $ ts1 ;
2021-03-24 15:38:43 +00:00
2021-04-02 19:58:48 +00:00
$ pvrl = ReadingsNum ( $ name , "Today_Hour" . sprintf ( "%02d" , $ chour ) . "_PVreal" , 0 ) ;
2021-04-04 07:45:35 +00:00
push @$ daref , "LastHourPVreal<>" . $ pvrl . " Wh<>" . $ ts1 ;
2021-03-24 15:38:43 +00:00
2021-04-02 19:58:48 +00:00
$ gcon = ReadingsNum ( $ name , "Today_Hour" . sprintf ( "%02d" , $ chour ) . "_GridConsumption" , 0 ) ;
2021-04-14 12:18:26 +00:00
push @$ daref , "LastHourGridconsumptionReal<>" . $ gcon . " Wh<>" . $ ts1 ;
2021-03-24 15:54:18 +00:00
2021-04-12 20:08:26 +00:00
## zusätzliche Events erzeugen - PV Vorhersage bis Ende des kommenden Tages
#############################################################################
for my $ idx ( sort keys % { $ data { $ type } { $ name } { nexthours } } ) {
my $ nhts = NexthoursVal ( $ hash , $ idx , "starttime" , undef ) ;
my $ nhfc = NexthoursVal ( $ hash , $ idx , "pvforecast" , undef ) ;
next if ( ! defined $ nhts || ! defined $ nhfc ) ;
my ( $ dt , $ h ) = $ nhts =~ /([\w-]+)\s(\d{2})/xs ;
2021-04-12 22:01:09 +00:00
push @$ daref , "AllPVforecastsToEvent<>" . $ nhfc . " Wh<>" . $ dt . " " . $ h . ":59:59" ;
2021-04-12 20:08:26 +00:00
}
## bestimmte einmalige Aktionen
##################################
my $ tlim = "00" ;
2021-03-24 15:38:43 +00:00
if ( $ chour =~ /^($tlim)$/x ) {
if ( ! exists $ hash - > { HELPER } { H00DONE } ) {
2021-04-02 12:20:29 +00:00
$ date = strftime "%Y-%m-%d" , localtime ( $ t - 7200 ) ; # Vortag (2 h Differenz reichen aus)
2021-04-02 19:58:48 +00:00
$ ts = $ date . " 23:59:59" ;
2021-03-24 15:38:43 +00:00
2021-04-02 19:58:48 +00:00
$ pvfc = ReadingsNum ( $ name , "Today_Hour24_PVforecast" , 0 ) ;
2021-04-12 20:08:26 +00:00
push @$ daref , "LastHourPVforecast<>" . $ pvfc . "<>" . $ ts ;
2021-03-24 15:38:43 +00:00
2021-04-02 19:58:48 +00:00
$ pvrl = ReadingsNum ( $ name , "Today_Hour24_PVreal" , 0 ) ;
2021-04-12 20:08:26 +00:00
push @$ daref , "LastHourPVreal<>" . $ pvrl . "<>" . $ ts ;
2021-03-24 15:38:43 +00:00
2021-04-02 19:58:48 +00:00
$ gcon = ReadingsNum ( $ name , "Today_Hour24_GridConsumption" , 0 ) ;
2021-06-10 20:32:37 +00:00
push @$ daref , "LastHourGridconsumptionReal<>" . $ gcon . "<>" . $ ts ;
writeDataToFile ( $ hash , "plantconfig" , $ plantcfg . $ name ) ; # Anlagenkonfiguration sichern
2021-03-24 15:54:18 +00:00
2021-04-10 08:31:37 +00:00
deleteReadingspec ( $ hash , "Today_Hour.*_Grid.*" ) ;
2021-03-24 15:38:43 +00:00
deleteReadingspec ( $ hash , "Today_Hour.*_PV.*" ) ;
2021-05-16 11:24:05 +00:00
deleteReadingspec ( $ hash , "Today_Hour.*_Bat.*" ) ;
2021-04-04 06:40:40 +00:00
deleteReadingspec ( $ hash , "powerTrigger_.*" ) ;
2021-06-10 20:32:37 +00:00
if ( ReadingsVal ( $ name , "pvCorrectionFactor_Auto" , "off" ) eq "on" ) {
for my $ n ( 1 .. 24 ) {
$ n = sprintf "%02d" , $ n ;
deleteReadingspec ( $ hash , "pvCorrectionFactor_${n}.*" ) ;
}
}
2021-03-24 15:38:43 +00:00
delete $ hash - > { HELPER } { INITCONTOTAL } ;
2021-04-07 08:49:17 +00:00
delete $ hash - > { HELPER } { INITFEEDTOTAL } ;
2021-05-09 18:29:53 +00:00
2021-05-02 20:24:39 +00:00
delete $ data { $ type } { $ name } { pvhist } { $ day } ; # den (alten) aktuellen Tag löschen
2021-05-15 07:36:53 +00:00
Log3 ( $ name , 3 , qq{ $name - history day "$day" deleted } ) ;
2021-05-09 18:29:53 +00:00
for my $ c ( keys % { $ data { $ type } { $ name } { consumers } } ) {
deleteConsumerPlanning ( $ hash , $ c ) ;
my $ calias = ConsumerVal ( $ hash , $ c , "alias" , "" ) ;
2021-05-15 07:36:53 +00:00
Log3 ( $ name , 3 , qq{ $name - Consumer planning of "$calias" deleted } ) ;
2021-05-09 18:29:53 +00:00
}
2021-06-13 19:18:25 +00:00
deleteReadingspec ( $ hash , "consumer.*_planned.*" ) ;
writeDataToFile ( $ hash , "consumers" , $ csmcache . $ name ) ; # Cache File Consumer schreiben
2021-05-09 18:29:53 +00:00
2021-03-24 15:38:43 +00:00
$ hash - > { HELPER } { H00DONE } = 1 ;
}
}
else {
delete $ hash - > { HELPER } { H00DONE } ;
2021-04-14 12:18:26 +00:00
}
2021-03-24 15:38:43 +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-04-17 07:37:23 +00:00
my $ raname = ReadingsVal ( $ name , "currentRadiationDev" , "" ) ; # Radiation Forecast Device
return if ( ! $ raname || ! $ defs { $ raname } ) ;
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 } ;
2021-04-11 07:54:28 +00:00
my $ uac = ReadingsVal ( $ name , "pvCorrectionFactor_Auto" , "off" ) ; # Auto- oder manuelle Korrektur
2021-04-17 07:37:23 +00:00
2021-04-25 15:33:22 +00:00
my $ err = checkdwdattr ( $ name , $ raname , \ @ draattrmust ) ;
$ paref - > { state } = $ err if ( $ err ) ;
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
2021-03-24 09:07:17 +00:00
if ( $ fd > 1 ) { # überhängende Werte löschen
delete $ data { $ type } { $ name } { nexthours } { "NextHour" . sprintf ( "%02d" , $ num ) } ;
2021-03-18 16:32:44 +00:00
next ;
}
2020-12-13 17:29:15 +00:00
2021-03-25 15:10:54 +00:00
my $ fh1 = $ fh + 1 ;
my $ fh2 = $ fh1 == 24 ? 23 : $ fh1 ;
2021-04-17 07:37:23 +00:00
my $ rad = ReadingsVal ( $ raname , "fc${fd}_${fh2}_Rad1h" , 0 ) ;
2021-03-28 08:24:48 +00:00
2021-05-15 07:36:53 +00:00
Log3 ( $ name , 5 , "$name - collect Radiation data: device=$raname, rad=fc${fd}_${fh2}_Rad1h, Rad1h=$rad" ) ;
2021-03-25 15:10:54 +00:00
2021-03-28 08:24:48 +00:00
my $ params = {
hash = > $ hash ,
name = > $ name ,
rad = > $ rad ,
t = > $ t ,
num = > $ num ,
2021-04-11 07:54:28 +00:00
uac = > $ uac ,
2021-03-28 08:24:48 +00:00
fh = > $ fh ,
2021-04-23 18:06:08 +00:00
fd = > $ fd ,
day = > $ paref - > { day }
2021-03-28 08:24:48 +00:00
} ;
2020-12-16 19:15:48 +00:00
2021-04-14 12:18:26 +00:00
my $ calcpv = calcPVforecast ( $ params ) ; # Vorhersage gewichtet kalkulieren
2021-03-20 21:26:12 +00:00
2021-04-12 20:08:26 +00:00
$ time_str = "NextHour" . sprintf "%02d" , $ num ;
$ epoche = $ t + ( 3600 * $ num ) ;
2021-05-11 16:42:32 +00:00
my ( $ ta , $ tsdef , $ realts ) = timestampToTimestring ( $ epoche ) ;
2021-03-28 08:24:48 +00:00
$ data { $ type } { $ name } { nexthours } { $ time_str } { pvforecast } = $ calcpv ;
2021-04-12 20:08:26 +00:00
$ data { $ type } { $ name } { nexthours } { $ time_str } { starttime } = $ tsdef ;
2021-05-09 18:29:53 +00:00
$ data { $ type } { $ name } { nexthours } { $ time_str } { today } = $ fd == 0 ? 1 : 0 ;
2021-03-28 08:24:48 +00:00
$ data { $ type } { $ name } { nexthours } { $ time_str } { Rad1h } = $ rad ; # nur Info: original Vorhersage Strahlungsdaten
2020-12-13 17:29:15 +00:00
2021-03-23 16:49:43 +00:00
if ( $ num < 23 && $ fh < 24 ) { # Ringspeicher PV forecast Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350
2021-03-25 15:10:54 +00:00
$ data { $ type } { $ name } { circular } { sprintf ( "%02d" , $ fh1 ) } { pvfc } = $ calcpv ;
2021-03-19 13:37:06 +00:00
}
2021-03-28 08:24:48 +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-04-04 07:45:35 +00:00
push @$ daref , "Today_Hour" . sprintf ( "%02d" , $ fh1 ) . "_PVforecast<>$calcpv Wh" ;
2021-01-17 19:13:02 +00:00
}
2021-03-25 15:10:54 +00:00
if ( $ fd == 0 && $ fh1 ) {
2021-03-13 20:15:09 +00:00
$ paref - > { calcpv } = $ calcpv ;
$ paref - > { histname } = "pvfc" ;
2021-03-25 15:10:54 +00:00
$ paref - > { nhour } = sprintf ( "%02d" , $ fh1 ) ;
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
}
2021-03-24 09:07:17 +00:00
2021-04-14 12:18:26 +00:00
push @$ daref , ".lastupdateForecastValues<>" . $ t ; # Statusreading letzter DWD update
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 } ;
2021-04-17 07:37:23 +00:00
my $ fcname = ReadingsVal ( $ name , "currentForecastDev" , "" ) ; # Weather Forecast Device
2020-12-27 18:51:53 +00:00
return if ( ! $ fcname || ! $ defs { $ fcname } ) ;
2021-04-25 15:33:22 +00:00
my $ err = checkdwdattr ( $ name , $ fcname , \ @ dweattrmust ) ;
$ paref - > { state } = $ err if ( $ err ) ;
2021-04-17 07:37:23 +00:00
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
2021-04-04 07:45:35 +00:00
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-04-02 19:58:48 +00:00
2021-04-04 07:45:35 +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
2021-03-25 15:10:54 +00:00
for my $ num ( 0 .. 46 ) {
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-25 15:10:54 +00:00
my $ fh1 = $ fh + 1 ;
my $ fh2 = $ fh1 == 24 ? 23 : $ fh1 ;
my $ wid = ReadingsNum ( $ fcname , "fc${fd}_${fh2}_ww" , - 1 ) ;
my $ neff = ReadingsNum ( $ fcname , "fc${fd}_${fh2}_Neff" , 0 ) ; # Effektive Wolkendecke
my $ r101 = ReadingsNum ( $ fcname , "fc${fd}_${fh2}_R101" , 0 ) ; # Niederschlagswahrscheinlichkeit> 0,1 mm während der letzten Stunde
2021-04-18 16:37:33 +00:00
my $ temp = ReadingsNum ( $ fcname , "fc${fd}_${fh2}_TTT" , 0 ) ; # Außentemperatur
2020-12-27 18:51:53 +00:00
2021-03-25 15:10:54 +00:00
my $ fhstr = sprintf "%02d" , $ fh ; # hier kann Tag/Nacht-Grenze verstellt werden
2020-12-13 17:29:15 +00:00
2021-03-21 21:21:16 +00:00
if ( $ fd == 0 && ( $ fhstr lt $ fc0_SunRise_round || $ fhstr gt $ fc0_SunSet_round ) ) { # Zeit vor Sonnenaufgang oder nach Sonnenuntergang heute
$ wid += 100 ; # "1" der WeatherID voranstellen wenn Nacht
2020-12-13 17:29:15 +00:00
}
2021-03-21 21:21:16 +00:00
elsif ( $ fd == 1 && ( $ fhstr lt $ fc1_SunRise_round || $ fhstr gt $ fc1_SunSet_round ) ) { # Zeit vor Sonnenaufgang oder nach Sonnenuntergang morgen
$ wid += 100 ; # "1" der WeatherID voranstellen wenn Nacht
2020-12-13 17:29:15 +00:00
}
2021-03-25 15:10:54 +00:00
my $ txt = ReadingsVal ( $ fcname , "fc${fd}_${fh2}_wwd" , '' ) ;
2021-03-16 14:53:54 +00:00
2021-05-15 07:36:53 +00:00
Log3 ( $ name , 5 , "$name - collect Weather data: device=$fcname, wid=fc${fd}_${fh1}_ww, val=$wid, txt=$txt, cc=$neff, rp=$r101, t=$temp" ) ;
2021-01-01 14:36:19 +00:00
2021-03-28 08:24:48 +00:00
$ time_str = "NextHour" . sprintf "%02d" , $ num ;
$ 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-04-18 16:37:33 +00:00
$ data { $ type } { $ name } { nexthours } { $ time_str } { temp } = $ temp ;
2021-03-20 21:26:12 +00:00
2021-04-11 07:54:28 +00:00
if ( $ num < 23 && $ fh < 24 ) { # Ringspeicher Weather Forum: https://forum.fhem.de/index.php/topic,117864.msg1139251.html#msg1139251
$ data { $ type } { $ name } { circular } { sprintf ( "%02d" , $ fh1 ) } { weatherid } = $ wid ;
$ data { $ type } { $ name } { circular } { sprintf ( "%02d" , $ fh1 ) } { weathertxt } = $ txt ;
$ data { $ type } { $ name } { circular } { sprintf ( "%02d" , $ fh1 ) } { wcc } = $ neff ;
$ data { $ type } { $ name } { circular } { sprintf ( "%02d" , $ fh1 ) } { wrp } = $ r101 ;
2021-04-18 16:37:33 +00:00
$ data { $ type } { $ name } { circular } { sprintf ( "%02d" , $ fh1 ) } { temp } = $ temp ;
if ( $ num == 0 ) { # aktuelle Außentemperatur
$ data { $ type } { $ name } { current } { temp } = $ temp ;
}
2021-03-18 17:25:37 +00:00
}
2021-03-23 22:01:47 +00:00
2021-04-18 16:37:33 +00:00
if ( $ fd == 0 && $ fh1 ) { # Weather in pvhistory speichern
2021-03-23 22:01:47 +00:00
$ paref - > { wid } = $ wid ;
$ paref - > { histname } = "weatherid" ;
2021-03-25 15:10:54 +00:00
$ paref - > { nhour } = sprintf ( "%02d" , $ fh1 ) ;
2021-03-23 22:01:47 +00:00
setPVhistory ( $ paref ) ;
2021-04-07 16:05:08 +00:00
$ paref - > { wcc } = $ neff ;
$ paref - > { histname } = "weathercloudcover" ;
setPVhistory ( $ paref ) ;
$ paref - > { wrp } = $ r101 ;
$ paref - > { histname } = "weatherrainprob" ;
setPVhistory ( $ paref ) ;
2021-04-18 16:37:33 +00:00
$ paref - > { temp } = $ temp ;
$ paref - > { histname } = "temperature" ;
setPVhistory ( $ paref ) ;
2021-03-23 22:01:47 +00:00
delete $ paref - > { histname } ;
}
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-24 15:38:43 +00:00
my $ type = $ hash - > { TYPE } ;
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-05-15 07:36:53 +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)
2021-05-28 20:57:10 +00:00
$ pv = $ pv < 0 ? 0 : $ pv ; # Forum: https://forum.fhem.de/index.php/topic,117864.msg1159718.html#msg1159718
2021-04-04 07:45:35 +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-04-04 19:24:55 +00:00
push @ { $ data { $ type } { $ name } { current } { genslidereg } } , $ pv ; # Schieberegister PV Erzeugung
2021-04-05 14:54:45 +00:00
limitArray ( $ data { $ type } { $ name } { current } { genslidereg } , $ defslidenum ) ;
2021-04-04 19:24:55 +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
2021-05-14 06:59:34 +00:00
my $ nhour = $ chour + 1 ;
2021-03-16 14:53:54 +00:00
2021-06-15 15:45:56 +00:00
my $ histetot = HistoryVal ( $ hash , $ day , sprintf ( "%02d" , $ nhour ) , "etotal" , 0 ) ; # etotal zu Beginn einer Stunde
2020-12-13 17:29:15 +00:00
2021-05-14 06:59:34 +00:00
my $ ethishour ;
2021-06-15 15:45:56 +00:00
if ( ! $ histetot ) { # etotal der aktuelle Stunde gesetzt ?
2021-05-14 06:59:34 +00:00
$ paref - > { etotal } = $ etotal ;
$ paref - > { nhour } = sprintf ( "%02d" , $ nhour ) ;
$ paref - > { histname } = "etotal" ;
setPVhistory ( $ paref ) ;
delete $ paref - > { histname } ;
2021-05-16 11:24:05 +00:00
my $ etot = CurrentVal ( $ hash , "etotal" , $ etotal ) ;
2021-05-14 06:59:34 +00:00
$ ethishour = int ( $ etotal - $ etot ) ;
2021-03-16 14:53:54 +00:00
}
else {
2021-05-14 06:59:34 +00:00
$ ethishour = int ( $ etotal - $ histetot ) ;
2021-03-16 14:53:54 +00:00
}
2020-12-13 17:29:15 +00:00
2021-05-14 06:59:34 +00:00
$ data { $ type } { $ name } { current } { etotal } = $ etotal ; # aktuellen etotal des WR speichern
if ( $ ethishour < 0 ) {
$ ethishour = 0 ;
2021-03-16 14:53:54 +00:00
}
2021-05-14 06:59:34 +00:00
push @$ daref , "Today_Hour" . sprintf ( "%02d" , $ nhour ) . "_PVreal<>" . $ ethishour . " Wh" ;
$ data { $ type } { $ name } { circular } { sprintf ( "%02d" , $ nhour ) } { pvrl } = $ ethishour ; # Ringspeicher PV real Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350
$ paref - > { ethishour } = $ ethishour ;
$ paref - > { nhour } = sprintf ( "%02d" , $ nhour ) ;
$ paref - > { histname } = "pvrl" ;
setPVhistory ( $ paref ) ;
delete $ paref - > { histname } ;
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 $ type = $ hash - > { TYPE } ;
2020-12-15 13:41:10 +00:00
2021-03-17 20:06:19 +00:00
my ( $ gc , $ gcunit ) = split ":" , $ h - > { gcon } ; # Readingname/Unit für aktuellen Netzbezug
2021-04-06 15:50:16 +00:00
my ( $ gf , $ gfunit ) = split ":" , $ h - > { gfeedin } ; # Readingname/Unit für aktuelle Netzeinspeisung
2021-03-17 20:06:19 +00:00
my ( $ gt , $ ctunit ) = split ":" , $ h - > { contotal } ; # Readingname/Unit für Bezug total
2021-04-06 15:50:16 +00:00
my ( $ ft , $ ftunit ) = split ":" , $ h - > { feedtotal } ; # Readingname/Unit für Einspeisung total
2021-03-17 20:06:19 +00:00
2021-04-06 15:50:16 +00:00
return if ( ! $ gc || ! $ gf || ! $ gt || ! $ ft ) ;
2021-03-17 20:06:19 +00:00
2021-04-08 19:53:31 +00:00
$ gfunit // = $ gcunit ;
2021-04-09 16:11:17 +00:00
$ gcunit // = $ gfunit ;
2021-05-15 07:36:53 +00:00
Log3 ( $ name , 5 , "$name - collect Meter data: device=$medev, gcon=$gc ($gcunit), gfeedin=$gf ($gfunit) ,contotal=$gt ($ctunit), feedtotal=$ft ($ftunit)" ) ;
2020-12-15 13:41:10 +00:00
2021-04-09 21:09:20 +00:00
my ( $ gco , $ gfin ) ;
my $ gcuf = $ gcunit =~ /^kW$/xi ? 1000 : 1 ;
my $ gfuf = $ gfunit =~ /^kW$/xi ? 1000 : 1 ;
2021-04-19 17:28:14 +00:00
$ gco = ReadingsNum ( $ medev , $ gc , 0 ) * $ gcuf ; # aktueller Bezug (W)
$ gfin = ReadingsNum ( $ medev , $ gf , 0 ) * $ gfuf ; # aktuelle Einspeisung (W)
2021-04-09 21:09:20 +00:00
2021-05-17 19:43:55 +00:00
my $ params ;
2021-05-16 11:24:05 +00:00
if ( $ gc eq "-gfeedin" ) { # Spezialfall gcon bei neg. gfeedin # Spezialfall: bei negativen gfeedin -> $gco = abs($gf), $gf = 0
2021-05-17 19:43:55 +00:00
$ params = {
dev = > $ medev ,
rdg = > $ gf ,
rdgf = > $ gfuf
} ;
( $ gfin , $ gco ) = substSpecialCases ( $ params ) ;
2021-04-09 16:11:17 +00:00
}
2021-04-06 15:50:16 +00:00
2021-05-16 11:24:05 +00:00
if ( $ gf eq "-gcon" ) { # Spezialfall gfeedin bei neg. gcon
2021-05-17 19:43:55 +00:00
$ params = {
dev = > $ medev ,
rdg = > $ gc ,
rdgf = > $ gcuf
} ;
( $ gco , $ gfin ) = substSpecialCases ( $ params ) ;
2021-04-06 20:25:38 +00:00
}
2021-04-06 15:50:16 +00:00
push @$ daref , "Current_GridConsumption<>" . ( int $ gco ) . " W" ;
$ data { $ type } { $ name } { current } { gridconsumption } = int $ gco ; # Hilfshash Wert current grid consumption Forum: https://forum.fhem.de/index.php/topic,117864.msg1139251.html#msg1139251
push @$ daref , "Current_GridFeedIn<>" . ( int $ gfin ) . " W" ;
$ data { $ type } { $ name } { current } { gridfeedin } = int $ gfin ; # Hilfshash Wert current grid Feed in
2021-04-04 19:24:55 +00:00
2021-03-17 20:06:19 +00:00
my $ ctuf = $ ctunit =~ /^kWh$/xi ? 1000 : 1 ;
2021-04-06 15:50:16 +00:00
my $ gctotal = ReadingsNum ( $ medev , $ gt , 0 ) * $ ctuf ; # Bezug total (Wh)
my $ ftuf = $ ftunit =~ /^kWh$/xi ? 1000 : 1 ;
my $ fitotal = ReadingsNum ( $ medev , $ ft , 0 ) * $ ftuf ; # Einspeisung total (Wh)
2021-03-17 20:06:19 +00:00
2021-04-02 06:37:48 +00:00
my $ gcdaypast = 0 ;
2021-04-06 15:50:16 +00:00
my $ gfdaypast = 0 ;
2021-03-17 20:06:19 +00:00
2021-04-06 15:50:16 +00:00
for my $ hour ( 0 .. int $ chour ) { # alle bisherigen Erzeugungen des Tages summieren
2021-04-02 06:37:48 +00:00
$ gcdaypast += ReadingsNum ( $ name , "Today_Hour" . sprintf ( "%02d" , $ hour ) . "_GridConsumption" , 0 ) ;
2021-04-06 15:50:16 +00:00
$ gfdaypast += ReadingsNum ( $ name , "Today_Hour" . sprintf ( "%02d" , $ hour ) . "_GridFeedIn" , 0 ) ;
2021-03-17 20:06:19 +00:00
}
2021-04-06 15:50:16 +00:00
my $ docon = 0 ;
if ( $ gcdaypast == 0 ) { # Management der Stundenberechnung auf Basis Totalwerte GridConsumtion
2021-03-17 20:06:19 +00:00
if ( defined $ hash - > { HELPER } { INITCONTOTAL } ) {
2021-04-06 15:50:16 +00:00
$ docon = 1 ;
2021-03-17 20:06:19 +00:00
}
else {
$ hash - > { HELPER } { INITCONTOTAL } = $ gctotal ;
}
}
elsif ( ! defined $ hash - > { HELPER } { INITCONTOTAL } ) {
2021-04-02 06:37:48 +00:00
$ hash - > { HELPER } { INITCONTOTAL } = $ gctotal - $ gcdaypast - ReadingsNum ( $ name , "Today_Hour" . sprintf ( "%02d" , $ chour + 1 ) . "_GridConsumption" , 0 ) ;
2021-03-17 20:06:19 +00:00
}
else {
2021-04-06 15:50:16 +00:00
$ docon = 1 ;
2021-03-17 20:06:19 +00:00
}
2021-04-06 15:50:16 +00:00
if ( $ docon ) {
2021-04-02 06:37:48 +00:00
my $ gctotthishour = int ( $ gctotal - ( $ gcdaypast + $ hash - > { HELPER } { INITCONTOTAL } ) ) ;
2021-03-17 20:06:19 +00:00
if ( $ gctotthishour < 0 ) {
$ gctotthishour = 0 ;
}
my $ nhour = $ chour + 1 ;
2021-04-04 07:45:35 +00:00
push @$ daref , "Today_Hour" . sprintf ( "%02d" , $ nhour ) . "_GridConsumption<>" . $ gctotthishour . " Wh" ;
2021-04-06 15:50:16 +00:00
$ data { $ type } { $ name } { circular } { sprintf ( "%02d" , $ nhour ) } { gcons } = $ gctotthishour ; # Hilfshash Wert Bezug (Wh) Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350
2021-03-17 20:06:19 +00:00
$ paref - > { gctotthishour } = $ gctotthishour ;
2021-03-23 16:49:43 +00:00
$ paref - > { nhour } = sprintf ( "%02d" , $ nhour ) ;
$ paref - > { histname } = "cons" ;
2021-03-17 20:06:19 +00:00
setPVhistory ( $ paref ) ;
delete $ paref - > { histname } ;
}
2021-04-06 15:50:16 +00:00
my $ dofeed = 0 ;
if ( $ gfdaypast == 0 ) { # Management der Stundenberechnung auf Basis Totalwerte GridFeedIn
if ( defined $ hash - > { HELPER } { INITFEEDTOTAL } ) {
$ dofeed = 1 ;
}
else {
$ hash - > { HELPER } { INITFEEDTOTAL } = $ fitotal ;
}
}
elsif ( ! defined $ hash - > { HELPER } { INITFEEDTOTAL } ) {
$ hash - > { HELPER } { INITFEEDTOTAL } = $ fitotal - $ gfdaypast - ReadingsNum ( $ name , "Today_Hour" . sprintf ( "%02d" , $ chour + 1 ) . "_GridFeedIn" , 0 ) ;
}
else {
$ dofeed = 1 ;
}
if ( $ dofeed ) {
my $ gftotthishour = int ( $ fitotal - ( $ gfdaypast + $ hash - > { HELPER } { INITFEEDTOTAL } ) ) ;
if ( $ gftotthishour < 0 ) {
$ gftotthishour = 0 ;
}
my $ nhour = $ chour + 1 ;
push @$ daref , "Today_Hour" . sprintf ( "%02d" , $ nhour ) . "_GridFeedIn<>" . $ gftotthishour . " Wh" ;
$ data { $ type } { $ name } { circular } { sprintf ( "%02d" , $ nhour ) } { gfeedin } = $ gftotthishour ;
$ paref - > { gftotthishour } = $ gftotthishour ;
$ paref - > { nhour } = sprintf ( "%02d" , $ nhour ) ;
$ paref - > { histname } = "gfeedin" ;
setPVhistory ( $ paref ) ;
delete $ paref - > { histname } ;
}
2020-12-13 17:29:15 +00:00
return ;
}
2021-05-02 20:24:39 +00:00
################################################################
# Consumer - Energieverbrauch aufnehmen
# - Masterdata ergänzen
# - Schaltzeiten planen
################################################################
sub _manageConsumerData {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ chour = $ paref - > { chour } ;
my $ day = $ paref - > { day } ;
my $ daref = $ paref - > { daref } ;
my $ nhour = $ chour + 1 ;
my $ type = $ hash - > { TYPE } ;
my $ acref = $ data { $ type } { $ name } { consumers } ;
for my $ c ( sort { $ a <=> $ b } keys % { $ acref } ) {
my $ consumer = $ acref - > { $ c } { name } ;
my $ alias = $ acref - > { $ c } { alias } ;
## Verbrauch auslesen + speichern
##################################
my $ enread = $ acref - > { $ c } { retotal } ;
my $ u = $ acref - > { $ c } { uetotal } ;
if ( $ enread ) {
my $ eu = $ u =~ /^kWh$/xi ? 1000 : 1 ;
my $ etot = ReadingsNum ( $ consumer , $ enread , 0 ) * $ eu ; # Summe Energieverbrauch des Verbrauchers
my $ ehist = HistoryVal ( $ hash , $ day , sprintf ( "%02d" , $ nhour ) , "csmt${c}" , undef ) ; # gespeicherter Totalverbrauch
if ( defined $ ehist && $ etot >= $ ehist ) {
my $ consumerco = $ etot - $ ehist ;
$ consumerco += HistoryVal ( $ hash , $ day , sprintf ( "%02d" , $ nhour ) , "csme${c}" , 0 ) ;
$ paref - > { consumerco } = $ consumerco ;
$ paref - > { nhour } = sprintf ( "%02d" , $ nhour ) ;
$ paref - > { histname } = "csme${c}" ;
setPVhistory ( $ paref ) ;
delete $ paref - > { histname } ;
}
$ paref - > { consumerco } = $ etot ;
$ paref - > { nhour } = sprintf ( "%02d" , $ nhour ) ;
$ paref - > { histname } = "csmt${c}" ;
setPVhistory ( $ paref ) ;
delete $ paref - > { histname } ;
}
## Durchschnittsverbrauch ermitteln + speichern
############################################################
my $ consumerco = 0 ;
my $ runhours = 0 ;
my $ dnum = 0 ;
for my $ n ( sort { $ a <=> $ b } keys % { $ data { $ type } { $ name } { pvhist } } ) { # gemessenen Verbrauch ermitteln
my $ csme = HistoryVal ( $ hash , $ n , 99 , "csme${c}" , 0 ) ;
next if ( ! $ csme ) ;
my $ hours = HistoryVal ( $ hash , $ n , 99 , "hourscsme${c}" , 0 ) ;
$ consumerco += $ csme ;
$ runhours += $ hours ;
$ dnum + + ;
}
if ( $ dnum ) {
2021-05-09 18:29:53 +00:00
$ data { $ type } { $ name } { consumers } { $ c } { avgenergy } = ceil ( $ consumerco / $ dnum ) ; # Durchschnittsverbrauch eines Tages aus History
2021-05-02 20:24:39 +00:00
$ data { $ type } { $ name } { consumers } { $ c } { mintime } = ( ceil ( $ runhours / $ dnum ) ) * 60 ; # Durchschnittslaufzeit in Minuten
}
2021-05-11 16:42:32 +00:00
$ paref - > { consumer } = $ c ;
__calcEnergyPieces ( $ paref ) ; # Energieverbrauch auf einzelne Stunden für Planungsgrundlage aufteilen
__planSwitchTimes ( $ paref ) ; # Consumer Switch Zeiten planen
__switchConsumer ( $ paref ) ; # Consumer schalten
2021-05-02 20:24:39 +00:00
## consumer Hash ergänzen, Reading generieren
###############################################
2021-06-12 08:37:50 +00:00
my $ costate = ReadingsVal ( $ consumer , "state" , "" ) ;
my ( $ pstate , $ starttime , $ stoptime ) = __planningStateandTimes ( $ paref ) ;
2021-05-02 20:24:39 +00:00
$ data { $ type } { $ name } { consumers } { $ c } { state } = $ costate ;
2021-05-09 18:29:53 +00:00
push @$ daref , "consumer${c}<>" . "name='$alias' state='$costate' planningstate='$pstate' " ; # Consumer Infos
2021-06-12 08:37:50 +00:00
push @$ daref , "consumer${c}_planned_start<>" . "$starttime" if ( $ starttime ) ; # Consumer Start geplant
push @$ daref , "consumer${c}_planned_stop<>" . "$stoptime" if ( $ stoptime ) ; # Consumer Stop geplant
2021-05-02 20:24:39 +00:00
}
2021-05-11 16:42:32 +00:00
delete $ paref - > { consumer } ;
2021-05-02 20:24:39 +00:00
return ;
}
###################################################################
# Energieverbrauch auf einzelne Stunden für Planungsgrundlage
# aufteilen
###################################################################
sub __calcEnergyPieces {
2021-05-11 16:42:32 +00:00
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ c = $ paref - > { consumer } ;
2021-05-02 20:24:39 +00:00
2021-05-11 16:42:32 +00:00
my $ type = $ hash - > { TYPE } ;
2021-05-02 20:24:39 +00:00
delete $ data { $ type } { $ name } { consumers } { $ c } { epieces } ;
my $ cotype = ConsumerVal ( $ hash , $ c , "type" , $ defctype ) ;
my $ mintime = ConsumerVal ( $ hash , $ c , "mintime" , $ defmintime ) ;
my $ hours = ceil ( $ mintime / 60 ) ; # Laufzeit in h
my $ ctote = ConsumerVal ( $ hash , $ c , "avgenergy" , undef ) ; # gemessener nominaler Energieverbrauch in Wh
$ ctote // = ConsumerVal ( $ hash , $ c , "power" , 0 ) * $ hours * $ hef { $ cotype } { tot } ; # alternativer nominaler Energieverbrauch in Wh
my $ epiecef = $ ctote * $ hef { $ cotype } { f } ; # Gewichtung erste Laufstunde
my $ epiecel = $ ctote * $ hef { $ cotype } { l } ; # Gewichtung letzte Laufstunde
my $ epiecem = 0 ;
if ( $ hours - 2 < 0 ) {
$ epiecem = $ ctote * $ hef { $ cotype } { m } ;
}
elsif ( $ hours - 2 == 0 ) {
$ epiecem = ( $ ctote * $ hef { $ cotype } { m } ) / 2 ;
$ epiecef += $ epiecem ;
$ epiecel += $ epiecem ;
}
else {
$ epiecem = ( $ ctote * $ hef { $ cotype } { m } ) / ( $ hours - 2 ) ;
}
for my $ h ( 1 .. $ hours ) {
2021-05-17 18:39:29 +00:00
my $ he ;
$ he = $ epiecef if ( $ h == 1 ) ; # kalk. Energieverbrauch Startstunde
$ he = $ epiecem if ( $ h > 1 && $ h < $ hours ) ; # kalk. Energieverbrauch Folgestunde(n)
$ he = $ epiecel if ( $ h == $ hours ) ; # kalk. Energieverbrauch letzte Stunde
2021-05-02 20:24:39 +00:00
2021-05-17 18:39:29 +00:00
$ he = $ epiecef + $ epiecel + $ epiecem if ( $ h == $ hours && $ hours == 1 ) ; # kalk. Energieverbrauch wenn max. 1 Stunde Laufzeit
2021-05-02 20:24:39 +00:00
2021-05-16 14:15:00 +00:00
$ data { $ type } { $ name } { consumers } { $ c } { epieces } { $ { h } } = sprintf ( '%.2f' , $ he ) ;
2021-05-02 20:24:39 +00:00
}
return ;
}
2021-05-09 18:29:53 +00:00
###################################################################
# Consumer Schaltzeiten planen
###################################################################
sub __planSwitchTimes {
2021-05-11 16:42:32 +00:00
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ c = $ paref - > { consumer } ;
2021-05-09 18:29:53 +00:00
2021-06-13 09:43:58 +00:00
return if ( ConsumerVal ( $ hash , $ c , "planstate" , undef ) ) ; # Verbraucher ist schon geplant/gestartet/fertig
2021-05-09 18:29:53 +00:00
2021-05-11 16:42:32 +00:00
my $ type = $ hash - > { TYPE } ;
2021-06-14 18:20:10 +00:00
my $ debug = AttrVal ( $ name , "debug" , 0 ) ;
2021-05-09 18:29:53 +00:00
my $ nh = $ data { $ type } { $ name } { nexthours } ;
my $ maxkey = ( scalar keys % { $ data { $ type } { $ name } { nexthours } } ) - 1 ;
my % max ;
my % mtimes ;
## max. Überschuß ermitteln
#############################
for my $ idx ( sort keys % { $ nh } ) {
my $ pvfc = NexthoursVal ( $ hash , $ idx , "pvforecast" , 0 ) ;
my $ confc = NexthoursVal ( $ hash , $ idx , "confc" , 0 ) ;
2021-06-01 21:05:53 +00:00
my $ surplus = $ pvfc - $ confc ; # Energieüberschuß
2021-05-09 18:29:53 +00:00
next if ( $ surplus <= 0 ) ;
my ( $ hour ) = $ idx =~ /NextHour(\d+)/xs ;
$ max { $ surplus } { starttime } = NexthoursVal ( $ hash , $ idx , "starttime" , "" ) ;
$ max { $ surplus } { today } = NexthoursVal ( $ hash , $ idx , "today" , 0 ) ;
$ max { $ surplus } { nexthour } = int ( $ hour ) ;
}
my $ order = 1 ;
for my $ k ( reverse sort { $ a <=> $ b } keys % max ) {
$ max { $ order } { surplus } = $ k ;
$ max { $ order } { starttime } = $ max { $ k } { starttime } ;
$ max { $ order } { nexthour } = $ max { $ k } { nexthour } ;
$ max { $ order } { today } = $ max { $ k } { today } ;
my $ ts = timestringToTimestamp ( $ max { $ k } { starttime } ) ;
$ mtimes { $ ts } { surplus } = $ k ;
$ mtimes { $ ts } { starttime } = $ max { $ k } { starttime } ;
$ mtimes { $ ts } { nexthour } = $ max { $ k } { nexthour } ;
$ mtimes { $ ts } { today } = $ max { $ k } { today } ;
delete $ max { $ k } ;
$ order + + ;
}
my $ epiece1 = ( ~ 0 >> 1 ) ;
my $ epieces = ConsumerVal ( $ hash , $ c , "epieces" , "" ) ;
2021-05-11 16:42:32 +00:00
2021-05-09 18:29:53 +00:00
if ( ref $ epieces eq "HASH" ) {
$ epiece1 = $ data { $ type } { $ name } { consumers } { $ c } { epieces } { 1 } ;
}
else {
return ;
}
2021-06-14 18:20:10 +00:00
if ( $ debug ) { # nur für Debugging
Log ( 1 , "DEBUG> $name - consumer: $c, epiece1: $epiece1" ) ;
}
2021-05-09 18:29:53 +00:00
my $ mode = ConsumerVal ( $ hash , $ c , "mode" , "can" ) ;
my $ calias = ConsumerVal ( $ hash , $ c , "alias" , "" ) ;
my $ mintime = ConsumerVal ( $ hash , $ c , "mintime" , $ defmintime ) ;
my $ stopdiff = ceil ( $ mintime / 60 ) * 3600 ;
2021-05-16 20:50:19 +00:00
$ paref - > { maxref } = \ % max ;
$ paref - > { mintime } = $ mintime ;
$ paref - > { stopdiff } = $ stopdiff ;
2021-05-11 16:42:32 +00:00
if ( $ mode eq "can" ) { # Verbraucher kann geplant werden
2021-06-14 18:20:10 +00:00
if ( $ debug ) { # nur für Debugging
2021-06-13 09:43:58 +00:00
Log ( 1 , "DEBUG> $name - consumer: $c, mode: $mode, relevant hash: mtimes" ) ;
for my $ m ( sort { $ a <=> $ b } keys % mtimes ) {
Log ( 1 , "DEBUG> $name - hash: mtimes, surplus: $mtimes{$m}{surplus}, starttime: $mtimes{$m}{starttime}, nexthour: $mtimes{$m}{nexthour}, today: $mtimes{$m}{today}" ) ;
}
}
2021-05-09 18:29:53 +00:00
for my $ ts ( sort { $ a <=> $ b } keys % mtimes ) {
2021-05-11 16:42:32 +00:00
if ( $ mtimes { $ ts } { surplus } >= $ epiece1 ) { # die früheste Startzeit sofern Überschuß größer als Bedarf
2021-06-01 21:05:53 +00:00
my $ starttime = $ mtimes { $ ts } { starttime } ;
$ paref - > { starttime } = $ starttime ;
$ starttime = ___switchonTimelimits ( $ paref ) ;
delete $ paref - > { starttime } ;
my $ startts = timestringToTimestamp ( $ starttime ) ; # Unix Timestamp für geplanten Switch on
my $ stopts = $ startts + $ stopdiff ;
my ( undef , undef , undef , $ stoptime ) = timestampToTimestring ( $ stopts ) ;
2021-05-09 18:29:53 +00:00
$ data { $ type } { $ name } { consumers } { $ c } { planswitchon } = $ startts ;
$ data { $ type } { $ name } { consumers } { $ c } { planswitchoff } = $ stopts ;
2021-06-01 21:05:53 +00:00
$ data { $ type } { $ name } { consumers } { $ c } { planstate } = "planned: " . $ starttime . " - " . $ stoptime ;
2021-05-09 18:29:53 +00:00
last ;
}
2021-06-14 18:20:10 +00:00
else {
$ data { $ type } { $ name } { consumers } { $ c } { planstate } = "not planned: the max expected surplus is less $epiece1" ;
}
2021-05-09 18:29:53 +00:00
}
}
2021-06-01 21:05:53 +00:00
else { # Verbraucher _muß_ geplant werden
2021-06-14 18:20:10 +00:00
if ( $ debug ) { # nur für Debugging
2021-06-13 09:43:58 +00:00
Log ( 1 , "DEBUG> $name - consumer: $c, mode: $mode, relevant hash: max" ) ;
for my $ o ( sort { $ a <=> $ b } keys % max ) {
Log ( 1 , "DEBUG> $name - hash: max, surplus: $max{$o}{surplus}, starttime: $max{$o}{starttime}, nexthour: $max{$o}{nexthour}, today: $max{$o}{today}" ) ;
}
}
2021-05-09 18:29:53 +00:00
for my $ o ( sort { $ a <=> $ b } keys % max ) {
2021-06-01 21:05:53 +00:00
next if ( ! $ max { $ o } { today } ) ; # der max-Wert ist _nicht_ heute
2021-05-16 20:50:19 +00:00
$ paref - > { elem } = $ o ;
___planMust ( $ paref ) ;
2021-05-09 18:29:53 +00:00
last ;
2021-05-16 19:29:59 +00:00
}
2021-06-01 21:05:53 +00:00
if ( ! ConsumerVal ( $ hash , $ c , "planstate" , undef ) ) { # es konnte keine Planung mit max für den aktuellen Tag erstellt werden -> Zwangsplanung mit ersten Wert
2021-05-16 20:50:19 +00:00
my $ p = ( sort { $ a <=> $ b } keys % max ) [ 0 ] ;
$ paref - > { elem } = $ p ;
___planMust ( $ paref ) ;
}
2021-05-09 18:29:53 +00:00
}
2021-05-09 19:14:27 +00:00
my $ planstate = ConsumerVal ( $ hash , $ c , "planstate" , "" ) ;
2021-06-13 19:18:25 +00:00
writeDataToFile ( $ hash , "consumers" , $ csmcache . $ name ) ; # Cache File Consumer schreiben
2021-05-15 07:36:53 +00:00
Log3 ( $ name , 3 , qq{ $name - Consumer "$calias" $planstate } ) if ( $ planstate ) ;
2021-05-11 16:42:32 +00:00
return ;
}
2021-05-16 20:50:19 +00:00
################################################################
# Consumer Zeiten MUST planen
################################################################
sub ___planMust {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ c = $ paref - > { consumer } ;
my $ maxref = $ paref - > { maxref } ;
my $ elem = $ paref - > { elem } ;
my $ mintime = $ paref - > { mintime } ;
my $ stopdiff = $ paref - > { stopdiff } ;
my $ type = $ hash - > { TYPE } ;
2021-06-01 21:05:53 +00:00
2021-05-16 20:50:19 +00:00
my $ maxts = timestringToTimestamp ( $ maxref - > { $ elem } { starttime } ) ; # Unix Timestamp des max. Überschusses heute
my $ half = ceil ( $ mintime / 2 / 60 ) ; # die halbe Gesamtlaufzeit in h als Vorlaufzeit einkalkulieren
my $ startts = $ maxts - ( $ half * 3600 ) ;
my ( undef , undef , undef , $ starttime ) = timestampToTimestring ( $ startts ) ;
2021-06-01 21:05:53 +00:00
$ paref - > { starttime } = $ starttime ;
$ starttime = ___switchonTimelimits ( $ paref ) ;
delete $ paref - > { starttime } ;
$ startts = timestringToTimestamp ( $ starttime ) ;
2021-05-16 20:50:19 +00:00
my $ stopts = $ startts + $ stopdiff ;
my ( undef , undef , undef , $ stoptime ) = timestampToTimestring ( $ stopts ) ;
$ data { $ type } { $ name } { consumers } { $ c } { planstate } = "planned: " . $ starttime . " - " . $ stoptime ;
$ data { $ type } { $ name } { consumers } { $ c } { planswitchon } = $ startts ; # Unix Timestamp für geplanten Switch on
$ data { $ type } { $ name } { consumers } { $ c } { planswitchoff } = $ stopts ; # Unix Timestamp für geplanten Switch off
return ;
}
2021-06-01 21:05:53 +00:00
################################################################
# Einschaltgrenzen berücksichtigen und Korrektur
# zurück liefern
################################################################
sub ___switchonTimelimits {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ c = $ paref - > { consumer } ;
my $ starttime = $ paref - > { starttime } ;
my $ origtime = $ starttime ;
2021-06-02 05:10:41 +00:00
my $ notbefore = ConsumerVal ( $ hash , $ c , "notbefore" , 0 ) ;
my $ notafter = ConsumerVal ( $ hash , $ c , "notafter" , 0 ) ;
2021-06-01 21:05:53 +00:00
my ( $ starthour ) = $ starttime =~ /\s(\d{2}):/xs ;
2021-06-02 05:10:41 +00:00
my $ change = q{ } ;
2021-06-01 21:05:53 +00:00
2021-06-02 05:10:41 +00:00
if ( $ notbefore && int $ starthour < int $ notbefore ) {
2021-06-01 21:05:53 +00:00
$ starthour = $ notbefore ;
2021-06-02 05:10:41 +00:00
$ change = "notbefore" ;
2021-06-01 21:05:53 +00:00
}
2021-06-02 05:10:41 +00:00
if ( $ notafter && int $ starthour > int $ notafter ) {
2021-06-01 21:05:53 +00:00
$ starthour = $ notafter ;
2021-06-02 05:10:41 +00:00
$ change = "notafter" ;
2021-06-01 21:05:53 +00:00
}
$ starthour = sprintf ( "%02d" , $ starthour ) ;
$ starttime =~ s/\s(\d{2}):/ $starthour:/x ;
2021-06-02 05:10:41 +00:00
if ( $ change ) {
2021-06-01 21:05:53 +00:00
my $ cname = ConsumerVal ( $ hash , $ c , "name" , "" ) ;
2021-06-02 06:35:42 +00:00
Log3 ( $ name , 3 , qq{ $name - Planned starttime "$cname" changed from "$origtime" to "$starttime" due to $change condition } ) ;
2021-06-01 21:05:53 +00:00
}
return $ starttime ;
}
2021-05-11 16:42:32 +00:00
################################################################
# Planungsdaten Consumer prüfen und ggf. starten/stoppen
################################################################
sub __switchConsumer {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ c = $ paref - > { consumer } ;
2021-06-13 09:43:58 +00:00
my $ t = $ paref - > { t } ; # aktueller Unixtimestamp
2021-05-11 16:42:32 +00:00
my $ state = $ paref - > { state } ;
my $ type = $ hash - > { TYPE } ;
2021-05-16 19:29:59 +00:00
my $ startts = ConsumerVal ( $ hash , $ c , "planswitchon" , undef ) ; # geplante Unix Startzeit
my $ stopts = ConsumerVal ( $ hash , $ c , "planswitchoff" , undef ) ; # geplante Unix Stopzeit
my $ pstate = ConsumerVal ( $ hash , $ c , "planstate" , "" ) ;
my $ cname = ConsumerVal ( $ hash , $ c , "name" , "" ) ; # Consumer Device Name
my $ calias = ConsumerVal ( $ hash , $ c , "alias" , "" ) ; # Consumer Device Alias
2021-05-11 16:42:32 +00:00
my $ stoptime ;
2021-06-12 12:32:43 +00:00
## Ist Verbraucher empfohlen ?
################################
if ( $ startts && $ t >= $ startts && $ stopts && $ t <= $ stopts ) {
$ data { $ type } { $ name } { consumers } { $ c } { isConsumptionRecommended } = 1 ;
}
else {
$ data { $ type } { $ name } { consumers } { $ c } { isConsumptionRecommended } = 0 ;
}
2021-05-11 16:42:32 +00:00
## Verbraucher einschalten
############################
2021-06-13 19:18:25 +00:00
my $ oncom = ConsumerVal ( $ hash , $ c , "oncom" , "" ) ; # Set Command für "on"
2021-06-01 21:05:53 +00:00
my $ auto = ConsumerVal ( $ hash , $ c , "auto" , 1 ) ;
2021-05-28 11:45:28 +00:00
2021-06-13 19:18:25 +00:00
if ( $ auto && $ oncom && $ pstate =~ /planned/xs && $ startts && $ t >= $ startts ) { # Verbraucher Start ist geplant && Startzeit überschritten
my $ surplus = CurrentVal ( $ hash , "surplus" , 0 ) ; # aktueller Überschuß
my $ mode = ConsumerVal ( $ hash , $ c , "mode" , $ defcmode ) ; # Consumer Planungsmode
my $ power = ConsumerVal ( $ hash , $ c , "power" , 0 ) ; # Consumer nominale Leistungsaufnahme (W)
2021-05-11 16:42:32 +00:00
2021-06-13 19:18:25 +00:00
if ( $ mode eq "must" || $ surplus >= $ power ) { # "Muss"-Planung oder Überschuß > Leistungsaufnahme
2021-05-11 16:42:32 +00:00
CommandSet ( undef , "$cname $oncom" ) ;
my ( undef , undef , undef , $ starttime ) = timestampToTimestring ( $ t ) ;
my $ stopdiff = ceil ( ConsumerVal ( $ hash , $ c , "mintime" , $ defmintime ) / 60 ) * 3600 ;
( undef , undef , undef , $ stoptime ) = timestampToTimestring ( $ t + $ stopdiff ) ;
$ data { $ type } { $ name } { consumers } { $ c } { planstate } = "switched on: " . $ starttime . " - " . $ stoptime ;
$ data { $ type } { $ name } { consumers } { $ c } { planswitchon } = $ t ;
$ data { $ type } { $ name } { consumers } { $ c } { planswitchoff } = $ t + $ stopdiff ;
$ state = qq{ Consumer "$calias" switched on } ;
2021-06-13 19:18:25 +00:00
writeDataToFile ( $ hash , "consumers" , $ csmcache . $ name ) ; # Cache File Consumer schreiben
2021-06-03 08:21:24 +00:00
Log3 ( $ name , 2 , "$name - $state (Automatic = $auto)" ) ;
2021-05-11 16:42:32 +00:00
}
}
## Verbraucher ausschalten
############################
2021-06-01 21:05:53 +00:00
my $ offcom = ConsumerVal ( $ hash , $ c , "offcom" , "" ) ; # Set Command für "off"
if ( $ auto && $ offcom && $ pstate !~ /switched\soff/xs && $ stopts && $ t >= $ stopts ) { # Verbraucher nicht switched off && Stopzeit überschritten
2021-05-11 16:42:32 +00:00
CommandSet ( undef , "$cname $offcom" ) ;
2021-06-01 21:05:53 +00:00
( undef , undef , undef , $ stoptime ) = timestampToTimestring ( $ t ) ;
$ data { $ type } { $ name } { consumers } { $ c } { planstate } = "switched off: " . $ stoptime ;
$ data { $ type } { $ name } { consumers } { $ c } { planswitchoff } = $ t ; # tatsächliche Ausschaltzeit
$ state = qq{ Consumer "$calias" switched off } ;
2021-06-13 19:18:25 +00:00
writeDataToFile ( $ hash , "consumers" , $ csmcache . $ name ) ; # Cache File Consumer schreiben
2021-06-03 08:21:24 +00:00
Log3 ( $ name , 2 , "$name - $state (Automatic = $auto)" ) ;
2021-05-11 16:42:32 +00:00
}
$ paref - > { state } = $ state ;
2021-05-09 18:29:53 +00:00
return ;
}
2021-06-12 08:37:50 +00:00
###################################################################
# Consumer Planungsstatus mit Schaltzeiten liefern
###################################################################
sub __planningStateandTimes {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ c = $ paref - > { consumer } ;
my $ pstate = ConsumerVal ( $ hash , $ c , "planstate" , "" ) ;
$ pstate = $ pstate =~ /planned/xs ? "planned" :
$ pstate =~ /switched\son/xs ? "started" :
$ pstate =~ /switched\soff/xs ? "finished" :
"unknown" ;
my $ startts = ConsumerVal ( $ hash , $ c , "planswitchon" , "" ) ;
my $ stopts = ConsumerVal ( $ hash , $ c , "planswitchoff" , "" ) ;
2021-06-12 13:10:14 +00:00
my $ starttime = '' ;
my $ stoptime = '' ;
$ starttime = timestampToTimestring ( $ startts ) if ( $ startts ) ;
$ stoptime = timestampToTimestring ( $ stopts ) if ( $ stopts ) ;
2021-06-12 08:37:50 +00:00
return ( $ pstate , $ starttime , $ stoptime ) ;
}
2021-04-09 21:09:20 +00:00
################################################################
# Batteriewerte sammeln
################################################################
sub _transferBatteryValues {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
2021-05-16 11:24:05 +00:00
my $ chour = $ paref - > { chour } ;
my $ day = $ paref - > { day } ;
2021-04-09 21:09:20 +00:00
my $ daref = $ paref - > { daref } ;
my $ badev = ReadingsVal ( $ name , "currentBatteryDev" , "" ) ; # aktuelles Meter device für Batteriewerte
my ( $ a , $ h ) = parseParams ( $ badev ) ;
$ badev = $ a - > [ 0 ] // "" ;
return if ( ! $ badev || ! $ defs { $ badev } ) ;
my $ type = $ hash - > { TYPE } ;
2021-05-16 11:24:05 +00:00
my ( $ pin , $ piunit ) = split ":" , $ h - > { pin } ; # Readingname/Unit für aktuelle Batterieladung
my ( $ pou , $ pounit ) = split ":" , $ h - > { pout } ; # Readingname/Unit für aktuelle Batterieentladung
2021-06-03 08:21:24 +00:00
my ( $ bin , $ binunit ) = split ":" , $ h - > { intotal } // "-:-" ; # Readingname/Unit der total in die Batterie eingespeisten Energie (Zähler)
my ( $ bout , $ boutunit ) = split ":" , $ h - > { outtotal } // "-:-" ; # Readingname/Unit der total aus der Batterie entnommenen Energie (Zähler)
2021-05-27 20:23:40 +00:00
my $ batchr = $ h - > { charge } // "" ; # Readingname Ladezustand Batterie
2021-04-09 21:09:20 +00:00
return if ( ! $ pin || ! $ pou ) ;
2021-05-16 11:24:05 +00:00
$ pounit // = $ piunit ;
$ piunit // = $ pounit ;
$ boutunit // = $ binunit ;
$ binunit // = $ boutunit ;
2021-04-09 21:09:20 +00:00
2021-05-28 11:45:28 +00:00
Log3 ( $ name , 5 , "$name - collect Battery data: device=$badev, pin=$pin ($piunit), pout=$pou ($pounit), totalin: $bin ($binunit), totalout: $bout ($boutunit), charge: $batchr" ) ;
2021-04-09 21:09:20 +00:00
2021-05-16 11:24:05 +00:00
my $ piuf = $ piunit =~ /^kW$/xi ? 1000 : 1 ;
my $ pouf = $ pounit =~ /^kW$/xi ? 1000 : 1 ;
my $ binuf = $ binunit =~ /^kWh$/xi ? 1000 : 1 ;
my $ boutuf = $ boutunit =~ /^kWh$/xi ? 1000 : 1 ;
2021-04-09 21:09:20 +00:00
2021-06-03 08:21:24 +00:00
my $ pbo = ReadingsNum ( $ badev , $ pou , 0 ) * $ pouf ; # aktuelle Batterieentladung (W)
my $ pbi = ReadingsNum ( $ badev , $ pin , 0 ) * $ piuf ; # aktueller Batterieladung (W)
my $ btotout = ReadingsNum ( $ badev , $ bout , 0 ) * $ boutuf ; # totale Batterieentladung (Wh)
my $ btotin = ReadingsNum ( $ badev , $ bin , 0 ) * $ binuf ; # totale Batterieladung (Wh)
my $ batcharge = ReadingsNum ( $ badev , $ batchr , "-" ) ;
2021-04-09 21:09:20 +00:00
2021-05-16 11:24:05 +00:00
my $ params ;
2021-04-19 17:28:14 +00:00
2021-05-16 11:24:05 +00:00
if ( $ pin eq "-pout" ) { # Spezialfall pin bei neg. pout
$ params = {
dev = > $ badev ,
rdg = > $ pou ,
rdgf = > $ pouf
} ;
( $ pbo , $ pbi ) = substSpecialCases ( $ params ) ;
2021-04-09 21:09:20 +00:00
}
2021-05-16 11:24:05 +00:00
if ( $ pou eq "-pin" ) { # Spezialfall pout bei neg. pin
$ params = {
dev = > $ badev ,
rdg = > $ pin ,
rdgf = > $ piuf
} ;
( $ pbi , $ pbo ) = substSpecialCases ( $ params ) ;
}
my $ nhour = $ chour + 1 ;
######
my $ histbatintot = HistoryVal ( $ hash , $ day , sprintf ( "%02d" , $ nhour ) , "batintotal" , undef ) ; # totale Betterieladung zu Beginn einer Stunde
my $ batinthishour ;
if ( ! defined $ histbatintot ) { # totale Betterieladung der aktuelle Stunde gesetzt ?
$ paref - > { batintotal } = $ btotin ;
$ paref - > { nhour } = sprintf ( "%02d" , $ nhour ) ;
$ paref - > { histname } = "batintotal" ;
setPVhistory ( $ paref ) ;
delete $ paref - > { histname } ;
my $ bitot = CurrentVal ( $ hash , "batintotal" , $ btotin ) ;
$ batinthishour = int ( $ btotin - $ bitot ) ;
}
else {
$ batinthishour = int ( $ btotin - $ histbatintot ) ;
}
if ( $ batinthishour < 0 ) {
$ batinthishour = 0 ;
}
2021-05-27 20:23:40 +00:00
$ data { $ type } { $ name } { circular } { sprintf ( "%02d" , $ nhour ) } { batin } = $ batinthishour ; # Ringspeicher Battery In Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350
2021-05-16 11:24:05 +00:00
$ paref - > { batinthishour } = $ batinthishour ;
$ paref - > { nhour } = sprintf ( "%02d" , $ nhour ) ;
$ paref - > { histname } = "batinthishour" ;
setPVhistory ( $ paref ) ;
delete $ paref - > { histname } ;
######
my $ histbatouttot = HistoryVal ( $ hash , $ day , sprintf ( "%02d" , $ nhour ) , "batouttotal" , undef ) ; # totale Betterieladung zu Beginn einer Stunde
my $ batoutthishour ;
if ( ! defined $ histbatouttot ) { # totale Betterieladung der aktuelle Stunde gesetzt ?
$ paref - > { batouttotal } = $ btotout ;
$ paref - > { nhour } = sprintf ( "%02d" , $ nhour ) ;
$ paref - > { histname } = "batouttotal" ;
setPVhistory ( $ paref ) ;
delete $ paref - > { histname } ;
2021-05-27 20:23:40 +00:00
my $ botot = CurrentVal ( $ hash , "batouttotal" , $ btotout ) ;
2021-05-16 11:24:05 +00:00
$ batoutthishour = int ( $ btotout - $ botot ) ;
}
else {
$ batoutthishour = int ( $ btotout - $ histbatouttot ) ;
}
if ( $ batoutthishour < 0 ) {
$ batoutthishour = 0 ;
2021-04-09 21:09:20 +00:00
}
2021-05-16 11:24:05 +00:00
$ data { $ type } { $ name } { circular } { sprintf ( "%02d" , $ nhour ) } { batout } = $ batoutthishour ; # Ringspeicher Battery In Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350
$ paref - > { batoutthishour } = $ batoutthishour ;
$ paref - > { nhour } = sprintf ( "%02d" , $ nhour ) ;
$ paref - > { histname } = "batoutthishour" ;
setPVhistory ( $ paref ) ;
delete $ paref - > { histname } ;
######
2021-04-09 21:09:20 +00:00
2021-05-16 11:24:05 +00:00
push @$ daref , "Today_Hour" . sprintf ( "%02d" , $ nhour ) . "_BatIn<>" . $ batinthishour . " Wh" ;
push @$ daref , "Today_Hour" . sprintf ( "%02d" , $ nhour ) . "_BatOut<>" . $ batoutthishour . " Wh" ;
push @$ daref , "Current_PowerBatIn<>" . ( int $ pbi ) . " W" ;
2021-04-09 21:09:20 +00:00
push @$ daref , "Current_PowerBatOut<>" . ( int $ pbo ) . " W" ;
2021-05-27 20:23:40 +00:00
push @$ daref , "Current_BatCharge<>" . $ batcharge . " %" ;
2021-05-16 11:24:05 +00:00
$ data { $ type } { $ name } { current } { powerbatin } = int $ pbi ; # Hilfshash Wert aktuelle Batterieladung
2021-04-09 21:09:20 +00:00
$ data { $ type } { $ name } { current } { powerbatout } = int $ pbo ; # Hilfshash Wert aktuelle Batterieentladung
2021-05-16 11:24:05 +00:00
$ data { $ type } { $ name } { current } { batintotal } = int $ btotin ; # totale Batterieladung
$ data { $ type } { $ name } { current } { batouttotal } = int $ btotout ; # totale Batterieentladung
2021-05-27 20:23:40 +00:00
$ data { $ type } { $ name } { current } { batcharge } = $ batcharge ; # aktuelle Batterieladung
2021-05-16 11:24:05 +00:00
2021-04-09 21:09:20 +00:00
return ;
}
2021-04-24 07:37:41 +00:00
################################################################
# Energieverbrauch Vorhersage kalkulieren
#
# Es werden nur gleiche Wochentage (Mo ... So)
# zusammengefasst und der Durchschnitt ermittelt als
# Vorhersage
################################################################
sub _estConsumptionForecast {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ chour = $ paref - > { chour } ;
my $ t = $ paref - > { t } ;
my $ day = $ paref - > { day } ; # aktuelles Tagdatum (01...31)
my $ dayname = $ paref - > { dayname } ; # aktueller Tagname
2021-05-02 20:24:39 +00:00
my $ medev = ReadingsVal ( $ name , "currentMeterDev" , "" ) ; # aktuelles Meter device
my $ swdfcfc = AttrVal ( $ name , "sameWeekdaysForConsfc" , 0 ) ; # nutze nur gleiche Wochentage (Mo...So) für Verbrauchsvorhersage
2021-04-24 07:37:41 +00:00
my ( $ am , $ hm ) = parseParams ( $ medev ) ;
$ medev = $ am - > [ 0 ] // "" ;
return if ( ! $ medev || ! $ defs { $ medev } ) ;
my $ type = $ hash - > { TYPE } ;
2021-05-02 20:24:39 +00:00
my $ acref = $ data { $ type } { $ name } { consumers } ;
2021-04-24 07:37:41 +00:00
## Verbrauchsvorhersage für den nächsten Tag
##############################################
2021-05-02 20:24:39 +00:00
my $ tomorrow = strftime "%a" , localtime ( $ t + 86400 ) ; # Wochentagsname kommender Tag
my $ totcon = 0 ;
my $ dnum = 0 ;
my $ consumerco = 0 ;
my $ min = ( ~ 0 >> 1 ) ;
my $ max = - ( ~ 0 >> 1 ) ;
2021-04-24 07:37:41 +00:00
for my $ n ( sort { $ a <=> $ b } keys % { $ data { $ type } { $ name } { pvhist } } ) {
2021-05-02 20:24:39 +00:00
next if ( $ n eq $ dayname ) ; # aktuellen (unvollständigen) Tag nicht berücksichtigen
2021-04-24 07:37:41 +00:00
2021-05-02 20:24:39 +00:00
if ( $ swdfcfc ) { # nur gleiche Tage (Mo...So) einbeziehen
2021-04-24 07:37:41 +00:00
my $ hdn = HistoryVal ( $ hash , $ n , 99 , "dayname" , undef ) ;
next if ( ! $ hdn || $ hdn ne $ tomorrow ) ;
}
my $ dcon = HistoryVal ( $ hash , $ n , 99 , "con" , 0 ) ;
next if ( ! $ dcon ) ;
2021-05-02 20:24:39 +00:00
for my $ c ( sort { $ a <=> $ b } keys % { $ acref } ) { # Verbrauch aller registrierten Verbraucher aufaddieren
$ consumerco += HistoryVal ( $ hash , $ n , 99 , "csme${c}" , 0 ) ;
}
2021-04-24 07:37:41 +00:00
2021-05-02 20:24:39 +00:00
$ dcon -= $ consumerco if ( $ dcon >= $ consumerco ) ; # Verbrauch registrierter Verbraucher aus Verbrauch eliminieren
$ min = $ dcon if ( $ dcon < $ min ) ;
$ max = $ dcon if ( $ dcon > $ max ) ;
2021-04-28 16:09:16 +00:00
2021-04-24 07:37:41 +00:00
$ totcon += $ dcon ;
$ dnum + + ;
}
if ( $ dnum ) {
2021-04-28 16:09:16 +00:00
my $ ddiff = ( $ max - $ min ) / $ dnum ; # Glättungsdifferenz
2021-05-02 20:24:39 +00:00
my $ tomavg = int ( ( $ totcon / $ dnum ) - $ ddiff ) ;
2021-04-28 16:09:16 +00:00
$ data { $ type } { $ name } { current } { tomorrowconsumption } = $ tomavg ; # Durchschnittsverbrauch aller (gleicher) Wochentage
2021-05-02 20:24:39 +00:00
2021-06-01 10:46:44 +00:00
Log3 ( $ name , 4 , "$name - estimated Consumption for tomorrow: $tomavg, days for avg: $dnum, hist. consumption registered consumers: " . sprintf "%.2f" , $ consumerco ) ;
2021-04-24 07:37:41 +00:00
}
else {
$ data { $ type } { $ name } { current } { tomorrowconsumption } = "Wait for more days with a consumption figure" ;
}
## Verbrauchsvorhersage für die nächsten Stunden
##################################################
my $ conh = { "01" = > 0 , "02" = > 0 , "03" = > 0 , "04" = > 0 ,
"05" = > 0 , "06" = > 0 , "07" = > 0 , "08" = > 0 ,
"09" = > 0 , "10" = > 0 , "11" = > 0 , "12" = > 0 ,
"13" = > 0 , "14" = > 0 , "15" = > 0 , "16" = > 0 ,
"17" = > 0 , "18" = > 0 , "19" = > 0 , "20" = > 0 ,
"21" = > 0 , "22" = > 0 , "23" = > 0 , "24" = > 0 ,
} ;
for my $ k ( sort keys % { $ data { $ type } { $ name } { nexthours } } ) {
2021-05-11 16:42:32 +00:00
my $ nhtime = NexthoursVal ( $ hash , $ k , "starttime" , undef ) ; # Startzeit
next if ( ! $ nhtime ) ;
2021-04-24 07:37:41 +00:00
2021-05-02 20:24:39 +00:00
$ dnum = 0 ;
$ consumerco = 0 ;
$ min = ( ~ 0 >> 1 ) ;
$ max = - ( ~ 0 >> 1 ) ;
2021-05-11 16:42:32 +00:00
my $ utime = timestringToTimestamp ( $ nhtime ) ;
2021-05-02 20:24:39 +00:00
my $ nhday = strftime "%a" , localtime ( $ utime ) ; # Wochentagsname des NextHours Key
my $ nhhr = sprintf ( "%02d" , ( int ( strftime "%H" , localtime ( $ utime ) ) ) + 1 ) ; # Stunde des Tages vom NextHours Key (01,02,...24)
2021-04-24 07:37:41 +00:00
for my $ m ( sort { $ a <=> $ b } keys % { $ data { $ type } { $ name } { pvhist } } ) {
2021-05-02 20:24:39 +00:00
next if ( $ m eq $ day ) ; # next wenn gleicher Tag (Datum) wie heute
2021-04-24 07:37:41 +00:00
2021-05-02 20:24:39 +00:00
if ( $ swdfcfc ) { # nur gleiche Tage (Mo...So) einbeziehen
2021-04-24 07:37:41 +00:00
my $ hdn = HistoryVal ( $ hash , $ m , 99 , "dayname" , undef ) ;
next if ( ! $ hdn || $ hdn ne $ nhday ) ;
}
my $ hcon = HistoryVal ( $ hash , $ m , $ nhhr , "con" , 0 ) ;
next if ( ! $ hcon ) ;
2021-05-11 16:42:32 +00:00
for my $ c ( sort { $ a <=> $ b } keys % { $ acref } ) { # historischer Verbrauch aller registrierten Verbraucher aufaddieren
2021-05-02 20:24:39 +00:00
$ consumerco += HistoryVal ( $ hash , $ m , $ nhhr , "csme${c}" , 0 ) ;
}
$ hcon -= $ consumerco if ( $ hcon >= $ consumerco ) ; # Verbrauch registrierter Verbraucher aus Verbrauch eliminieren
2021-04-28 16:09:16 +00:00
$ min = $ hcon if ( $ hcon < $ min ) ;
$ max = $ hcon if ( $ hcon > $ max ) ;
2021-04-24 07:37:41 +00:00
$ conh - > { $ nhhr } += $ hcon ;
$ dnum + + ;
}
2021-05-09 18:29:53 +00:00
2021-04-24 07:37:41 +00:00
if ( $ dnum ) {
2021-05-02 20:24:39 +00:00
my $ hdiff = ( $ max - $ min ) / $ dnum ; # Glättungsdifferenz
my $ conavg = int ( ( $ conh - > { $ nhhr } / $ dnum ) - $ hdiff ) ;
$ data { $ type } { $ name } { nexthours } { $ k } { confc } = $ conavg ; # Durchschnittsverbrauch aller gleicher Wochentage pro Stunde
2021-05-12 11:12:59 +00:00
if ( NexthoursVal ( $ hash , $ k , "today" , 0 ) ) { # nur Werte des aktuellen Tag speichern
$ data { $ type } { $ name } { circular } { sprintf ( "%02d" , $ nhhr ) } { confc } = $ conavg ;
2021-05-11 16:42:32 +00:00
$ paref - > { confc } = $ conavg ;
$ paref - > { nhour } = sprintf ( "%02d" , $ nhhr ) ;
$ paref - > { histname } = "confc" ;
setPVhistory ( $ paref ) ;
delete $ paref - > { histname } ;
}
2021-06-01 10:46:44 +00:00
Log3 ( $ name , 4 , "$name - estimated Consumption for $nhday -> starttime: $nhtime, con: $conavg, days for avg: $dnum, hist. consumption registered consumers: " . sprintf "%.2f" , $ consumerco ) ;
2021-04-24 07:37:41 +00:00
}
}
return ;
}
2021-04-04 06:40:40 +00:00
################################################################
# Schwellenwerte auswerten und signalisieren
################################################################
sub _evaluateThresholds {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ daref = $ paref - > { daref } ;
2021-04-04 19:24:55 +00:00
my $ pt = ReadingsVal ( $ name , "powerTrigger" , "" ) ;
2021-04-05 14:54:45 +00:00
my $ eh4t = ReadingsVal ( $ name , "energyH4Trigger" , "" ) ;
2021-04-04 06:40:40 +00:00
2021-04-05 14:54:45 +00:00
if ( $ pt ) {
my $ aaref = CurrentVal ( $ hash , "genslidereg" , "" ) ;
my @ aa = ( ) ;
@ aa = @ { $ aaref } if ( ref $ aaref eq "ARRAY" ) ;
if ( scalar @ aa >= $ defslidenum ) {
$ paref - > { taref } = \ @ aa ;
$ paref - > { tname } = "powerTrigger" ;
$ paref - > { tholds } = $ pt ;
__evaluateArray ( $ paref ) ;
}
}
2021-04-04 06:40:40 +00:00
2021-04-05 14:54:45 +00:00
if ( $ eh4t ) {
my $ aaref = CurrentVal ( $ hash , "h4fcslidereg" , "" ) ;
my @ aa = ( ) ;
@ aa = @ { $ aaref } if ( ref $ aaref eq "ARRAY" ) ;
if ( scalar @ aa >= $ defslidenum ) {
$ paref - > { taref } = \ @ aa ;
$ paref - > { tname } = "energyH4Trigger" ;
$ paref - > { tholds } = $ eh4t ;
__evaluateArray ( $ paref ) ;
}
}
2021-04-04 19:24:55 +00:00
2021-04-05 14:54:45 +00:00
delete $ paref - > { taref } ;
delete $ paref - > { tname } ;
delete $ paref - > { tholds } ;
2021-04-04 19:24:55 +00:00
2021-04-05 14:54:45 +00:00
return ;
}
################################################################
# Threshold-Array auswerten und Readings vorbereiten
################################################################
sub __evaluateArray {
my $ paref = shift ;
my $ name = $ paref - > { name } ;
my $ daref = $ paref - > { daref } ;
my $ taref = $ paref - > { taref } ; # Referenz zum Threshold-Array
my $ tname = $ paref - > { tname } ; # Thresholdname, z.B. powerTrigger
my $ tholds = $ paref - > { tholds } ; # Triggervorgaben, z.B. aus Reading powerTrigger
my $ gen1 = @$ taref [ 0 ] ;
my $ gen2 = @$ taref [ 1 ] ;
my $ gen3 = @$ taref [ 2 ] ;
2021-04-04 19:24:55 +00:00
2021-04-05 14:54:45 +00:00
my ( $ a , $ h ) = parseParams ( $ tholds ) ;
2021-04-04 06:40:40 +00:00
for my $ key ( keys % { $ h } ) {
2021-04-04 07:45:35 +00:00
my ( $ knum , $ cond ) = $ key =~ /^([0-9]+)(on|off)$/x ;
2021-04-04 06:40:40 +00:00
2021-04-05 06:20:53 +00:00
if ( $ cond eq "on" && $ gen1 > $ h - > { $ key } ) {
next if ( $ gen2 < $ h - > { $ key } ) ;
next if ( $ gen3 < $ h - > { $ key } ) ;
2021-04-05 14:54:45 +00:00
push @$ daref , "${tname}_${knum}<>on" if ( ReadingsVal ( $ name , "${tname}_${knum}" , "off" ) eq "off" ) ;
2021-04-04 06:40:40 +00:00
}
2021-04-05 06:20:53 +00:00
if ( $ cond eq "off" && $ gen1 < $ h - > { $ key } ) {
next if ( $ gen2 > $ h - > { $ key } ) ;
next if ( $ gen3 > $ h - > { $ key } ) ;
2021-04-05 14:54:45 +00:00
push @$ daref , "${tname}_${knum}<>off" if ( ReadingsVal ( $ name , "${tname}_${knum}" , "on" ) eq "on" ) ;
2021-04-04 06:40:40 +00:00
}
}
return ;
}
################################################################
# Zusammenfassungen erstellen
################################################################
2021-04-05 08:29:56 +00:00
sub _calcSummaries {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ daref = $ paref - > { daref } ;
my $ chour = $ paref - > { chour } ; # aktuelle Stunde
my $ minute = $ paref - > { minute } ; # aktuelle Minute
2021-04-04 06:40:40 +00:00
2021-04-05 08:29:56 +00:00
my $ type = $ hash - > { TYPE } ;
$ minute = ( int $ minute ) + 1 ; # Minute Range umsetzen auf 1 bis 60
2021-05-16 14:36:17 +00:00
## Vorhersagen
################
2021-04-05 08:29:56 +00:00
my $ next1HoursSum = { "PV" = > 0 , "Consumption" = > 0 , "Total" = > 0 , "ConsumpRcmd" = > 0 } ;
my $ next2HoursSum = { "PV" = > 0 , "Consumption" = > 0 , "Total" = > 0 , "ConsumpRcmd" = > 0 } ;
my $ next3HoursSum = { "PV" = > 0 , "Consumption" = > 0 , "Total" = > 0 , "ConsumpRcmd" = > 0 } ;
2021-04-04 06:40:40 +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 } ;
my $ todaySum = { "PV" = > 0 , "Consumption" = > 0 , "Total" = > 0 , "ConsumpRcmd" = > 0 } ;
2021-04-05 08:29:56 +00:00
my $ rdh = 24 - $ chour - 1 ; # verbleibende Anzahl Stunden am Tag beginnend mit 00 (abzüglich aktuelle Stunde)
my $ remainminutes = 60 - $ minute ; # verbleibende Minuten der aktuellen Stunde
2021-04-04 06:40:40 +00:00
2021-04-21 10:53:14 +00:00
my $ restofhourpvfc = ( NexthoursVal ( $ hash , "NextHour00" , "pvforecast" , 0 ) ) / 60 * $ remainminutes ;
my $ restofhourconfc = ( NexthoursVal ( $ hash , "NextHour00" , "confc" , 0 ) ) / 60 * $ remainminutes ;
2021-04-05 08:29:56 +00:00
2021-04-21 10:53:14 +00:00
$ next1HoursSum - > { PV } = $ restofhourpvfc ;
$ next2HoursSum - > { PV } = $ restofhourpvfc ;
$ next3HoursSum - > { PV } = $ restofhourpvfc ;
$ next4HoursSum - > { PV } = $ restofhourpvfc ;
$ restOfDaySum - > { PV } = $ restofhourpvfc ;
$ next1HoursSum - > { Consumption } = $ restofhourconfc ;
$ next2HoursSum - > { Consumption } = $ restofhourconfc ;
$ next3HoursSum - > { Consumption } = $ restofhourconfc ;
$ next4HoursSum - > { Consumption } = $ restofhourconfc ;
$ restOfDaySum - > { Consumption } = $ restofhourconfc ;
2021-04-04 06:40:40 +00:00
for my $ h ( 1 .. 47 ) {
2021-04-21 10:53:14 +00:00
my $ pvfc = NexthoursVal ( $ hash , "NextHour" . sprintf ( "%02d" , $ h ) , "pvforecast" , 0 ) ;
my $ confc = NexthoursVal ( $ hash , "NextHour" . sprintf ( "%02d" , $ h ) , "confc" , 0 ) ;
2021-04-04 06:40:40 +00:00
2021-04-05 08:29:56 +00:00
if ( $ h == 1 ) {
2021-04-21 10:53:14 +00:00
$ next1HoursSum - > { PV } += $ pvfc / 60 * $ minute ;
$ next1HoursSum - > { Consumption } += $ confc / 60 * $ minute ;
2021-04-05 08:29:56 +00:00
}
if ( $ h <= 2 ) {
2021-04-21 10:53:14 +00:00
$ next2HoursSum - > { PV } += $ pvfc if ( $ h < 2 ) ;
$ next2HoursSum - > { PV } += $ pvfc / 60 * $ minute if ( $ h == 2 ) ;
$ next2HoursSum - > { Consumption } += $ confc if ( $ h < 2 ) ;
$ next2HoursSum - > { Consumption } += $ confc / 60 * $ minute if ( $ h == 2 ) ;
2021-04-05 08:29:56 +00:00
}
if ( $ h <= 3 ) {
2021-04-21 10:53:14 +00:00
$ next3HoursSum - > { PV } += $ pvfc if ( $ h < 3 ) ;
$ next3HoursSum - > { PV } += $ pvfc / 60 * $ minute if ( $ h == 3 ) ;
$ next3HoursSum - > { Consumption } += $ confc if ( $ h < 3 ) ;
$ next3HoursSum - > { Consumption } += $ confc / 60 * $ minute if ( $ h == 3 ) ;
2021-04-05 08:29:56 +00:00
}
if ( $ h <= 4 ) {
2021-04-21 10:53:14 +00:00
$ next4HoursSum - > { PV } += $ pvfc if ( $ h < 4 ) ;
$ next4HoursSum - > { PV } += $ pvfc / 60 * $ minute if ( $ h == 4 ) ;
$ next4HoursSum - > { Consumption } += $ confc if ( $ h < 4 ) ;
$ next4HoursSum - > { Consumption } += $ confc / 60 * $ minute if ( $ h == 4 ) ;
2021-04-05 08:29:56 +00:00
}
2021-04-21 10:53:14 +00:00
$ restOfDaySum - > { PV } += $ pvfc if ( $ h <= $ rdh ) ;
$ restOfDaySum - > { Consumption } += $ confc if ( $ h <= $ rdh ) ;
$ tomorrowSum - > { PV } += $ pvfc if ( $ h > $ rdh ) ;
2021-04-04 06:40:40 +00:00
}
for my $ th ( 1 .. 24 ) {
2021-04-18 16:37:33 +00:00
$ todaySum - > { PV } += ReadingsNum ( $ name , "Today_Hour" . sprintf ( "%02d" , $ th ) . "_PVforecast" , 0 ) ;
2021-04-04 06:40:40 +00:00
}
2021-04-05 14:54:45 +00:00
push @ { $ data { $ type } { $ name } { current } { h4fcslidereg } } , int $ next4HoursSum - > { PV } ; # Schieberegister 4h Summe Forecast
limitArray ( $ data { $ type } { $ name } { current } { h4fcslidereg } , $ defslidenum ) ;
2021-04-17 07:37:23 +00:00
my $ gcon = CurrentVal ( $ hash , "gridconsumption" , 0 ) ; # aktueller Netzbezug
2021-04-17 07:52:06 +00:00
my $ tconsum = CurrentVal ( $ hash , "tomorrowconsumption" , undef ) ; # Verbrauchsprognose für folgenden Tag
2021-04-17 07:37:23 +00:00
my $ pvgen = CurrentVal ( $ hash , "generation" , 0 ) ;
my $ gfeedin = CurrentVal ( $ hash , "gridfeedin" , 0 ) ;
my $ batin = CurrentVal ( $ hash , "powerbatin" , 0 ) ; # aktuelle Batterieladung
my $ batout = CurrentVal ( $ hash , "powerbatout" , 0 ) ; # aktuelle Batterieentladung
2021-04-09 21:09:20 +00:00
2021-04-24 18:31:26 +00:00
my $ consumption = int ( $ pvgen - $ gfeedin + $ gcon - $ batin + $ batout ) ;
2021-05-28 17:56:19 +00:00
my $ selfconsumption = int ( $ pvgen - $ gfeedin - $ batin ) ;
2021-05-30 07:54:58 +00:00
$ selfconsumption = $ selfconsumption < 0 ? 0 : $ selfconsumption ;
2021-05-28 17:56:19 +00:00
my $ surplus = int ( $ pvgen - $ consumption ) ; # aktueller Überschuß
2021-04-24 18:31:26 +00:00
my $ selfconsumptionrate = 0 ;
my $ autarkyrate = 0 ;
2021-04-25 15:33:22 +00:00
$ selfconsumptionrate = sprintf ( "%.0f" , $ selfconsumption / $ pvgen * 100 ) if ( $ pvgen ) ;
2021-04-24 18:31:26 +00:00
$ autarkyrate = sprintf ( "%.0f" , $ selfconsumption / ( $ selfconsumption + $ gcon ) * 100 ) if ( $ selfconsumption ) ;
2021-04-24 17:48:43 +00:00
2021-04-24 18:05:34 +00:00
$ data { $ type } { $ name } { current } { consumption } = $ consumption ;
$ data { $ type } { $ name } { current } { selfconsumption } = $ selfconsumption ;
$ data { $ type } { $ name } { current } { selfconsumptionrate } = $ selfconsumptionrate ;
$ data { $ type } { $ name } { current } { autarkyrate } = $ autarkyrate ;
2021-05-11 16:42:32 +00:00
$ data { $ type } { $ name } { current } { surplus } = $ surplus ;
2021-04-06 15:50:16 +00:00
2021-04-17 07:37:23 +00:00
push @$ daref , "Current_Consumption<>" . $ consumption . " W" ;
2021-04-24 17:48:43 +00:00
push @$ daref , "Current_SelfConsumption<>" . $ selfconsumption . " W" ;
2021-04-24 18:05:34 +00:00
push @$ daref , "Current_SelfConsumptionRate<>" . $ selfconsumptionrate . " %" ;
push @$ daref , "Current_AutarkyRate<>" . $ autarkyrate . " %" ;
2021-04-21 10:53:14 +00:00
2021-04-17 07:37:23 +00:00
push @$ daref , "NextHours_Sum01_PVforecast<>" . ( int $ next1HoursSum - > { PV } ) . " Wh" ;
push @$ daref , "NextHours_Sum02_PVforecast<>" . ( int $ next2HoursSum - > { PV } ) . " Wh" ;
push @$ daref , "NextHours_Sum03_PVforecast<>" . ( int $ next3HoursSum - > { PV } ) . " Wh" ;
push @$ daref , "NextHours_Sum04_PVforecast<>" . ( int $ next4HoursSum - > { PV } ) . " Wh" ;
push @$ daref , "RestOfDayPVforecast<>" . ( int $ restOfDaySum - > { PV } ) . " Wh" ;
push @$ daref , "Tomorrow_PVforecast<>" . ( int $ tomorrowSum - > { PV } ) . " Wh" ;
push @$ daref , "Today_PVforecast<>" . ( int $ todaySum - > { PV } ) . " Wh" ;
2021-04-04 06:40:40 +00:00
2021-04-21 10:53:14 +00:00
push @$ daref , "Tomorrow_ConsumptionForecast<>" . $ tconsum . " Wh" if ( defined $ tconsum ) ;
push @$ daref , "NextHours_Sum04_ConsumptionForecast<>" . ( int $ next4HoursSum - > { Consumption } ) . " Wh" ;
push @$ daref , "RestOfDayConsumptionForecast<>" . ( int $ restOfDaySum - > { Consumption } ) . " Wh" ;
2021-04-04 06:40:40 +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 ) ;
}
2021-05-16 11:24:05 +00:00
################################################################
# Spezialfall auflösen wenn Wert von $val2 dem
# Redingwert von $val1 entspricht sofern $val1 negativ ist
################################################################
sub substSpecialCases {
my $ paref = shift ;
my $ dev = $ paref - > { dev } ;
my $ rdg = $ paref - > { rdg } ;
my $ rdgf = $ paref - > { rdgf } ;
my $ val1 = ReadingsNum ( $ dev , $ rdg , 0 ) * $ rdgf ;
my $ val2 ;
if ( $ val1 <= 0 ) {
$ val2 = abs ( $ val1 ) ;
$ val1 = 0 ;
}
else {
$ val2 = 0 ;
}
return ( $ val1 , $ val2 ) ;
}
2021-04-14 12:18:26 +00:00
################################################################
# Energieverbrauch des Hauses in History speichern
################################################################
sub saveEnergyConsumption {
my $ paref = shift ;
my $ name = $ paref - > { name } ;
my $ chour = $ paref - > { chour } ;
2021-05-16 11:24:05 +00:00
my $ pvrl = ReadingsNum ( $ name , "Today_Hour" . sprintf ( "%02d" , $ chour + 1 ) . "_PVreal" , 0 ) ;
my $ gfeedin = ReadingsNum ( $ name , "Today_Hour" . sprintf ( "%02d" , $ chour + 1 ) . "_GridFeedIn" , 0 ) ;
my $ gcon = ReadingsNum ( $ name , "Today_Hour" . sprintf ( "%02d" , $ chour + 1 ) . "_GridConsumption" , 0 ) ;
2021-05-16 14:36:17 +00:00
my $ batin = ReadingsNum ( $ name , "Today_Hour" . sprintf ( "%02d" , $ chour + 1 ) . "_BatIn" , 0 ) ;
my $ batout = ReadingsNum ( $ name , "Today_Hour" . sprintf ( "%02d" , $ chour + 1 ) . "_BatOut" , 0 ) ;
2021-04-14 12:18:26 +00:00
2021-05-16 14:36:17 +00:00
my $ con = $ pvrl - $ gfeedin + $ gcon - $ batin + $ batout ;
2021-05-16 11:24:05 +00:00
$ paref - > { con } = $ con ;
$ paref - > { nhour } = sprintf ( "%02d" , $ chour + 1 ) ;
$ paref - > { histname } = "con" ;
setPVhistory ( $ paref ) ;
delete $ paref - > { histname } ;
2021-04-14 12:18:26 +00:00
return ;
}
2021-05-02 20:24:39 +00:00
################################################################
# Grunddaten aller registrierten Consumer speichern
################################################################
sub collectAllRegConsumers {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ type = $ hash - > { TYPE } ;
2021-05-30 07:54:58 +00:00
delete $ data { $ type } { $ name } { current } { consumerdevs } ;
2021-05-02 20:24:39 +00:00
for my $ c ( 1 .. $ maxconsumer ) {
$ c = sprintf "%02d" , $ c ;
my $ consumer = AttrVal ( $ name , "consumer${c}" , "" ) ;
next if ( ! $ consumer ) ;
my ( $ ac , $ hc ) = parseParams ( $ consumer ) ;
$ consumer = $ ac - > [ 0 ] // "" ;
if ( ! $ consumer || ! $ defs { $ consumer } ) {
my $ err = qq{ ERROR - the device "$consumer" doesn't exist anymore! Delete or change the attribute "consumer$ { c } ". } ;
Log3 ( $ name , 1 , "$name - $err" ) ;
next ;
}
2021-05-30 07:54:58 +00:00
push @ { $ data { $ type } { $ name } { current } { consumerdevs } } , $ consumer ; # alle Consumerdevices in CurrentHash eintragen
2021-05-02 20:24:39 +00:00
my $ alias = AttrVal ( $ consumer , "alias" , $ consumer ) ;
my ( $ rtot , $ utot ) ;
if ( exists $ hc - > { etotal } ) {
my $ etotal = $ hc - > { etotal } ;
( $ rtot , $ utot ) = split ":" , $ etotal ;
}
2021-06-15 15:45:56 +00:00
my $ rauto = $ hc - > { auto } // q{ } ;
2021-05-02 20:24:39 +00:00
my $ ctype = $ hc - > { type } // $ defctype ;
my $ hours = ( $ hc - > { mintime } // $ hef { $ ctype } { mt } ) / 60 ;
2021-05-29 12:56:28 +00:00
my $ avgenergy = $ hc - > { power } * $ hours * $ hef { $ ctype } { tot } ; # Wh
2021-06-15 15:45:56 +00:00
my $ auto = 1 ;
$ auto = ReadingsVal ( $ consumer , $ rauto , 1 ) if ( $ rauto ) ; # Reading für Ready-Bit -> Einschalten möglich ?
2021-05-29 12:56:28 +00:00
$ data { $ type } { $ name } { consumers } { $ c } { name } = $ consumer ; # Name des Verbrauchers (Device)
$ data { $ type } { $ name } { consumers } { $ c } { alias } = $ alias ; # Alias des Verbrauchers (Device)
2021-06-01 21:05:53 +00:00
$ data { $ type } { $ name } { consumers } { $ c } { type } = $ hc - > { type } // $ defctype ; # Typ des Verbrauchers
2021-05-29 12:56:28 +00:00
$ data { $ type } { $ name } { consumers } { $ c } { power } = $ hc - > { power } ; # Leistungsaufnahme des Verbrauchers in W
$ data { $ type } { $ name } { consumers } { $ c } { avgenergy } = $ avgenergy ; # Initialwert Energieverbrauch (evtl. Überschreiben in manageConsumerData)
2021-06-01 21:05:53 +00:00
$ data { $ type } { $ name } { consumers } { $ c } { mintime } = $ hc - > { mintime } // $ hef { $ ctype } { mt } ; # Initialwert min. Einschalt- bzw. Zykluszeit (evtl. Überschreiben in manageConsumerData)
$ data { $ type } { $ name } { consumers } { $ c } { mode } = $ hc - > { mode } // $ defcmode ; # Planungsmode des Verbrauchers
$ data { $ type } { $ name } { consumers } { $ c } { icon } = $ hc - > { icon } // q{ } ; # Icon für den Verbraucher
$ data { $ type } { $ name } { consumers } { $ c } { oncom } = $ hc - > { on } // q{ } ; # Setter Einschaltkommando
$ data { $ type } { $ name } { consumers } { $ c } { offcom } = $ hc - > { off } // q{ } ; # Setter Ausschaltkommando
2021-06-15 15:45:56 +00:00
$ data { $ type } { $ name } { consumers } { $ c } { autoreading } = $ rauto ; # Readingname zur Automatiksteuerung
2021-06-01 21:05:53 +00:00
$ data { $ type } { $ name } { consumers } { $ c } { auto } = $ auto ; # Automaticsteuerung: 1 - Automatic ein, 0 - Automatic aus
$ data { $ type } { $ name } { consumers } { $ c } { retotal } = $ rtot // q{ } ; # Reading der Leistungsmessung
$ data { $ type } { $ name } { consumers } { $ c } { uetotal } = $ utot // q{ } ; # Unit der Leistungsmessung
$ data { $ type } { $ name } { consumers } { $ c } { notbefore } = $ hc - > { notbefore } // q{ } ; # nicht einschalten vor Stunde in 24h Format (00-23)
$ data { $ type } { $ name } { consumers } { $ c } { notafter } = $ hc - > { notafter } // q{ } ; # nicht einschalten nach Stunde in 24h Format (00-23)
2021-05-02 20:24:39 +00:00
}
Log3 ( $ name , 5 , "$name - all registered consumers:\n" . Dumper $ data { $ type } { $ name } { consumers } ) ;
return ;
}
2020-12-13 17:29:15 +00:00
################################################################
# FHEMWEB Fn
################################################################
sub FwFn {
2021-05-27 20:23:40 +00:00
my ( $ FW_wname , $ name , $ room , $ pageHash ) = @ _ ; # pageHash is set for summaryFn.
2021-05-26 20:47:27 +00:00
my $ hash = $ defs { $ name } ;
2020-12-13 17:29:15 +00:00
2020-12-15 13:41:10 +00:00
RemoveInternalTimer ( $ hash , \ & pageRefresh ) ;
2020-12-13 17:29:15 +00:00
$ hash - > { HELPER } { FW } = $ FW_wname ;
2021-05-27 20:23:40 +00:00
my $ ret = entryGraphic ( $ name ) ;
2020-12-13 17:29:15 +00:00
# Autorefresh nur des aufrufenden FHEMWEB-Devices
2021-05-26 20:47:27 +00:00
my $ al = AttrVal ( $ name , "autoRefresh" , 0 ) ;
2020-12-13 17:29:15 +00:00
if ( $ al ) {
InternalTimer ( gettimeofday ( ) + $ al , \ & pageRefresh , $ hash , 0 ) ;
2021-05-26 20:47:27 +00:00
Log3 ( $ name , 5 , "$name - next start of autoRefresh: " . FmtDateTime ( gettimeofday ( ) + $ al ) ) ;
2020-12-13 17:29:15 +00:00
}
return $ ret ;
}
################################################################
sub pageRefresh {
2020-12-17 15:52:48 +00:00
my $ hash = shift ;
2021-05-26 20:47:27 +00:00
my $ name = $ hash - > { NAME } ;
2020-12-13 17:29:15 +00:00
# Seitenrefresh festgelegt durch SolarForecast-Attribut "autoRefresh" und "autoRefreshFW"
2021-05-26 20:47:27 +00:00
my $ rd = AttrVal ( $ name , "autoRefreshFW" , $ hash - > { HELPER } { FW } ) ;
2021-03-28 08:24:48 +00:00
{ map { FW_directNotify ( "#FHEMWEB:$_" , "location.reload('true')" , "" ) } $ rd } ## no critic 'Map blocks'
2020-12-13 17:29:15 +00:00
2021-05-26 20:47:27 +00:00
my $ al = AttrVal ( $ name , "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 ) ;
2021-05-26 20:47:27 +00:00
Log3 ( $ name , 5 , "$name - next start of autoRefresh: " . FmtDateTime ( gettimeofday ( ) + $ al ) ) ;
2020-12-13 17:29:15 +00:00
}
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
2021-05-27 20:23:40 +00:00
my $ ret = "<html>" ;
$ ret . = entryGraphic ( $ name ) ;
$ ret . = "</html>" ;
return $ ret ;
}
2020-12-13 17:29:15 +00:00
2021-05-27 20:23:40 +00:00
################################################################
# Einstieg Grafikanzeige
################################################################
sub entryGraphic {
my $ name = shift ;
my $ ftui = shift // "" ;
2020-12-13 17:29:15 +00:00
2021-05-27 20:23:40 +00:00
my $ hash = $ defs { $ name } ;
# Setup Vollständigkeit prüfen
###############################
my $ incomplete = _checkSetupComplete ( $ hash ) ;
return $ incomplete if ( $ incomplete ) ;
2021-05-28 19:47:21 +00:00
# Kontext des SolarForecast-Devices speichern für Refresh
##########################################################
$ hash - > { HELPER } { SPGDEV } = $ name ; # Name des aufrufenden SolarForecastSPG-Devices
$ hash - > { HELPER } { SPGROOM } = $ FW_room ? $ FW_room : "" ; # Raum aus dem das SolarForecastSPG-Device die Funktion aufrief
$ hash - > { HELPER } { SPGDETAIL } = $ FW_detail ? $ FW_detail : "" ; # Name des SolarForecastSPG-Devices (wenn Detailansicht)
2021-05-27 20:23:40 +00:00
# Parameter f. Anzeige extrahieren
###################################
2021-06-10 20:32:37 +00:00
my $ width = AttrNum ( $ name , 'beamWidth' , 6 ) ; # zu klein ist nicht problematisch
my $ maxhours = AttrNum ( $ name , 'hourCount' , 24 ) ;
my $ colorw = AttrVal ( $ name , 'weatherColor' , 'FFFFFF' ) ; # Wetter Icon Farbe
my $ alias = AttrVal ( $ name , "alias" , $ name ) ; # Linktext als Aliasname oder Devicename setzen
my $ gsel = AttrVal ( $ name , 'graphicSelect' , 'both' ) ; # Auswahl der anzuzeigenden Grafiken
my $ dlink = qq{ <a href="$FW_ME$FW_subdir?detail=$name">$alias</a> } ;
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
my $ w = $ width * $ maxhours ; # gesammte Breite der Ausgabe , WetterIcon braucht ca. 34px
2021-05-27 20:23:40 +00:00
2021-06-10 20:32:37 +00:00
my $ paref = {
2021-05-27 20:23:40 +00:00
hash = > $ hash ,
name = > $ name ,
ftui = > $ ftui ,
maxhours = > $ maxhours ,
2021-06-11 07:32:00 +00:00
modulo = > 1 ,
2021-06-02 21:26:06 +00:00
dstyle = > qq{ style='padding-left: 10px; padding-right: 10px; padding-top: 3px; padding-bottom: 3px;' } , # TD-Style
2021-05-27 20:23:40 +00:00
offset = > AttrNum ( $ name , 'historyHour' , 0 ) ,
hourstyle = > AttrVal ( $ name , 'hourStyle' , '' ) ,
colorfc = > AttrVal ( $ name , 'beam1Color' , '000000' ) ,
colorc = > AttrVal ( $ name , 'beam2Color' , 'C4C4A7' ) ,
fcolor1 = > AttrVal ( $ name , 'beam1FontColor' , 'C4C4A7' ) ,
fcolor2 = > AttrVal ( $ name , 'beam2FontColor' , '000000' ) ,
beam1cont = > AttrVal ( $ name , 'beam1Content' , 'pvForecast' ) ,
beam2cont = > AttrVal ( $ name , 'beam2Content' , 'pvForecast' ) ,
2021-06-12 12:32:43 +00:00
caicon = > AttrVal ( $ name , 'consumerAdviceIcon' , $ caicondef ) , # Consumer AdviceIcon
clegend = > AttrVal ( $ name , 'consumerLegend' , 'icon_top' ) , # Lage und Art Cunsumer Legende
2021-05-27 20:23:40 +00:00
lotype = > AttrVal ( $ name , 'layoutType' , 'single' ) ,
kw = > AttrVal ( $ name , 'Wh/kWh' , 'Wh' ) ,
height = > AttrNum ( $ name , 'beamHeight' , 200 ) ,
width = > $ width ,
fsize = > AttrNum ( $ name , 'spaceSize' , 24 ) ,
maxVal = > AttrNum ( $ name , 'maxValBeam' , 0 ) , # dyn. Anpassung der Balkenhöhe oder statisch ?
show_night = > AttrNum ( $ name , 'showNight' , 0 ) , # alle Balken (Spalten) anzeigen ?
show_diff = > AttrVal ( $ name , 'showDiff' , 'no' ) , # zusätzliche Anzeige $di{} in allen Typen
weather = > AttrNum ( $ name , 'showWeather' , 1 ) ,
colorw = > $ colorw ,
colorwn = > AttrVal ( $ name , 'weatherColorNight' , $ colorw ) , # Wetter Icon Farbe Nacht
wlalias = > AttrVal ( $ name , 'alias' , $ name ) ,
header = > AttrNum ( $ name , 'showHeader' , 1 ) ,
hdrDetail = > AttrVal ( $ name , 'headerDetail' , 'all' ) , # ermöglicht den Inhalt zu begrenzen, um bspw. passgenau in ftui einzubetten
lang = > AttrVal ( "global" , 'language' , 'EN' ) ,
flowgh = > AttrVal ( $ name , 'flowGraphicSize' , $ defflowGSize ) , # Größe Energieflußgrafik
2021-05-28 08:33:17 +00:00
flowgani = > AttrVal ( $ name , 'flowGraphicAnimate' , 0 ) , # Animation Energieflußgrafik
2021-06-12 09:24:58 +00:00
css = > AttrVal ( $ name , 'Css' , $ cssdef ) , # Css Styles
2021-05-27 20:23:40 +00:00
} ;
2021-06-11 07:32:00 +00:00
my $ ret = q{ } ;
2021-05-27 20:23:40 +00:00
if ( IsDisabled ( $ name ) ) {
2021-06-10 20:32:37 +00:00
$ ret . = "<table class='roomoverview'>" ;
$ ret . = "<tr style='height:" . $ paref - > { height } . "px'>" ;
$ ret . = "<td>" ;
$ ret . = qq{ SolarForecast device <a href="$FW_ME$FW_subdir?detail=$name">$name</a> is disabled } ;
$ ret . = "</td>" ;
$ ret . = "</tr>" ;
$ ret . = "</table>" ;
2020-12-13 17:29:15 +00:00
}
else {
$ ret . = "<span>$dlink </span><br>" if ( AttrVal ( $ name , "showLink" , 0 ) ) ;
2021-06-10 20:32:37 +00:00
$ ret . = "<html>" ;
$ ret . = $ html_start if ( defined ( $ html_start ) ) ;
$ ret . = "<style>TD.solarfc {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='solarfc'>" ;
# Headerzeile generieren
##########################
my $ header = _graphicHeader ( $ paref ) ;
$ paref - > { header } = $ header ;
# Verbraucherlegende und Steuerung
###################################
my $ legendtxt = _graphicConsumerLegend ( $ paref ) ;
$ paref - > { legendtxt } = $ legendtxt ;
$ ret . = "\n<table class='block'>" ; # das \n erleichtert das Lesen der debug Quelltextausgabe
2021-06-11 07:32:00 +00:00
my $ m = $ paref - > { modulo } % 2 ;
2021-06-10 20:32:37 +00:00
if ( $ header ) { # Header ausgeben
2021-06-11 07:32:00 +00:00
$ ret . = "<tr class='$htr{$m}{cl}'>" ;
2021-06-10 20:32:37 +00:00
$ ret . = "<td colspan='" . ( $ maxhours + 2 ) . "' align='center' style='word-break: normal'>$header</td>" ;
$ ret . = "</tr>" ;
2021-06-11 07:32:00 +00:00
$ paref - > { modulo } + + ;
2021-06-10 20:32:37 +00:00
}
my $ clegend = $ paref - > { clegend } ;
2021-06-11 07:32:00 +00:00
$ m = $ paref - > { modulo } % 2 ;
2021-06-10 20:32:37 +00:00
if ( $ legendtxt && ( $ clegend eq 'top' ) ) {
2021-06-11 07:32:00 +00:00
$ ret . = "<tr class='$htr{$m}{cl}'>" ;
2021-06-10 20:32:37 +00:00
$ ret . = "<td colspan='" . ( $ maxhours + 2 ) . "' align='center' style='word-break: normal'>$legendtxt</td>" ;
$ ret . = "</tr>" ;
2021-06-11 07:32:00 +00:00
$ paref - > { modulo } + + ;
2021-06-10 20:32:37 +00:00
}
2021-06-11 07:32:00 +00:00
$ m = $ paref - > { modulo } % 2 ;
2021-06-10 20:32:37 +00:00
if ( $ gsel eq "both" || $ gsel eq "forecast" ) {
my % hfch ;
2021-06-11 07:32:00 +00:00
my $ hfcg = \ % hfch ; #(hfcg = hash forecast graphic)
2021-06-10 20:32:37 +00:00
# Werte aktuelle Stunde
##########################
$ paref - > { hfcg } = $ hfcg ;
$ paref - > { thishour } = _beamGraphicFirstHour ( $ paref ) ;
# get consumer list and display it in Graphics
################################################
_showConsumerInGraphicBeam ( $ paref ) ;
# Werte restliche Stunden
###########################
my $ back = _beamGraphicRemainingHours ( $ paref ) ;
2021-06-11 07:32:00 +00:00
$ paref - > { maxVal } = $ back - > { maxVal } ; # Startwert wenn kein Wert bereits via attr vorgegeben ist
2021-06-10 20:32:37 +00:00
$ paref - > { maxCon } = $ back - > { maxCon } ;
2021-06-11 07:32:00 +00:00
$ paref - > { maxDif } = $ back - > { maxDif } ; # für Typ diff
$ paref - > { minDif } = $ back - > { minDif } ; # für Typ diff
2021-06-10 20:32:37 +00:00
#Log3 ($hash,3,Dumper($hfcg));
# Balkengrafik
################
$ ret . = _beamGraphic ( $ paref ) ;
}
2021-06-11 07:32:00 +00:00
$ m = $ paref - > { modulo } % 2 ;
2021-06-10 20:32:37 +00:00
if ( $ gsel eq "both" || $ gsel eq "flow" ) {
2021-06-11 07:32:00 +00:00
$ ret . = "<tr class='$htr{$m}{cl}'>" ;
2021-06-10 20:32:37 +00:00
my $ fg = _flowGraphic ( $ paref ) ;
$ ret . = "<td colspan='" . ( $ maxhours + 2 ) . "' align='center' style='word-break: normal'>$fg</td>" ;
$ ret . = "</tr>" ;
2021-06-11 07:32:00 +00:00
$ paref - > { modulo } + + ;
2021-06-10 20:32:37 +00:00
}
2021-06-11 07:32:00 +00:00
$ m = $ paref - > { modulo } % 2 ;
2021-06-10 20:32:37 +00:00
# Legende unten
#################
if ( $ legendtxt && ( $ clegend eq 'bottom' ) ) {
2021-06-11 07:32:00 +00:00
$ ret . = "<tr class='$htr{$m}{cl}'>" ;
2021-06-10 20:32:37 +00:00
$ ret . = "<td colspan='" . ( $ maxhours + 2 ) . "' align='center' style='word-break: normal'>" ;
$ ret . = "$legendtxt</td></tr>" ;
}
$ ret . = "</table>" ;
$ ret . = "</td></tr>" ;
$ ret . = "</table>" ;
$ ret . = $ html_end if ( defined ( $ html_end ) ) ;
$ ret . = "</html>" ;
2021-05-27 20:23:40 +00:00
}
2020-12-13 17:29:15 +00:00
return $ ret ;
}
2021-05-27 20:23:40 +00:00
################################################################
# Vollständigkeit Setup prüfen
################################################################
sub _checkSetupComplete {
my $ hash = shift ;
my $ ret = q{ } ;
my $ name = $ hash - > { NAME } ;
my $ is = ReadingsVal ( $ name , "inverterStrings" , undef ) ; # String Konfig
my $ fcdev = ReadingsVal ( $ name , "currentForecastDev" , undef ) ; # Forecast Device (Wetter)
my $ radev = ReadingsVal ( $ name , "currentRadiationDev" , undef ) ; # Forecast Device (Wetter)
my $ indev = ReadingsVal ( $ name , "currentInverterDev" , undef ) ; # Inverter Device
my $ medev = ReadingsVal ( $ name , "currentMeterDev" , undef ) ; # Meter Device
my $ peak = ReadingsVal ( $ name , "modulePeakString" , undef ) ; # String Peak
my $ pv0 = NexthoursVal ( $ hash , "NextHour00" , "pvforecast" , undef ) ;
my $ dir = ReadingsVal ( $ name , "moduleDirection" , undef ) ; # Modulausrichtung Konfig
my $ ta = ReadingsVal ( $ name , "moduleTiltAngle" , undef ) ; # Modul Neigungswinkel Konfig
if ( ! $ is || ! $ fcdev || ! $ radev || ! $ indev || ! $ medev || ! $ peak || ! defined $ pv0 || ! $ dir || ! $ ta ) {
my $ link = qq{ <a href="$FW_ME$FW_subdir?detail=$name">$name</a> } ;
my $ height = AttrNum ( $ name , 'beamHeight' , 200 ) ;
my $ lang = AttrVal ( "global" , "language" , "EN" ) ;
$ ret . = "<table class='roomoverview'>" ;
$ ret . = "<tr style='height:" . $ height . "px'>" ;
$ ret . = "<td>" ;
if ( ! $ fcdev ) { ## no critic 'Cascading'
$ ret . = $ hqtxt { cfd } { $ lang } ;
}
elsif ( ! $ radev ) {
$ ret . = $ hqtxt { crd } { $ lang } ;
}
elsif ( ! $ indev ) {
$ ret . = $ hqtxt { cid } { $ lang } ;
}
elsif ( ! $ medev ) {
$ ret . = $ hqtxt { mid } { $ lang } ;
}
elsif ( ! $ is ) {
$ ret . = $ hqtxt { ist } { $ lang } ;
}
elsif ( ! $ peak ) {
$ ret . = $ hqtxt { mps } { $ lang } ;
}
elsif ( ! $ dir ) {
$ ret . = $ hqtxt { mdr } { $ lang } ;
}
elsif ( ! $ ta ) {
$ ret . = $ hqtxt { mta } { $ lang } ;
}
elsif ( ! defined $ pv0 ) {
$ ret . = $ hqtxt { awd } { $ lang } ;
}
$ ret . = "</td>" ;
$ ret . = "</tr>" ;
$ ret . = "</table>" ;
$ ret =~ s/LINK/$link/gxs ;
return $ ret ;
}
return ;
}
2021-06-10 20:32:37 +00:00
################################################################
# forecastGraphic Headerzeile generieren
################################################################
sub _graphicHeader {
my $ paref = shift ;
my $ header = $ paref - > { header } ;
2021-06-03 16:44:26 +00:00
2021-06-10 20:32:37 +00:00
return if ( ! $ header ) ;
2020-12-13 17:29:15 +00:00
2021-06-10 20:32:37 +00:00
my $ hdrDetail = $ paref - > { hdrDetail } ; # ermöglicht den Inhalt zu begrenzen, um bspw. passgenau in ftui einzubetten
my $ ftui = $ paref - > { ftui } ;
my $ lang = $ paref - > { lang } ;
my $ name = $ paref - > { name } ;
my $ hash = $ paref - > { hash } ;
my $ kw = $ paref - > { kw } ;
my $ dstyle = $ paref - > { dstyle } ; # TD-Style
my $ lup = ReadingsTimestamp ( $ name , ".lastupdateForecastValues" , "0000-00-00 00:00:00" ) ; # letzter Forecast Update
my $ pcfa = ReadingsVal ( $ name , "pvCorrectionFactor_Auto" , "off" ) ;
my $ co4h = ReadingsNum ( $ name , "NextHours_Sum04_ConsumptionForecast" , 0 ) ;
my $ coRe = ReadingsNum ( $ name , "RestOfDayConsumptionForecast" , 0 ) ;
my $ coTo = ReadingsNum ( $ name , "Tomorrow_ConsumptionForecast" , 0 ) ;
my $ coCu = ReadingsNum ( $ name , "Current_Consumption" , 0 ) ;
my $ pv4h = ReadingsNum ( $ name , "NextHours_Sum04_PVforecast" , 0 ) ;
my $ pvRe = ReadingsNum ( $ name , "RestOfDayPVforecast" , 0 ) ;
my $ pvTo = ReadingsNum ( $ name , "Tomorrow_PVforecast" , 0 ) ;
my $ pvCu = ReadingsNum ( $ name , "Current_PV" , 0 ) ;
2021-05-30 07:54:58 +00:00
2021-06-10 20:32:37 +00:00
my $ pvcorrf00 = NexthoursVal ( $ hash , "NextHour00" , "pvcorrf" , "-/m" ) ;
my ( $ pcf , $ pcq ) = split "/" , $ pvcorrf00 ;
my $ pvcanz = "factor: $pcf / quality: $pcq" ;
$ pcq =~ s/m/-1/xs ;
my $ pvfc00 = NexthoursVal ( $ hash , "NextHour00" , "pvforecast" , undef ) ;
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" ;
}
2021-05-26 20:47:27 +00:00
2021-06-10 20:32:37 +00:00
my $ lupt = "last update:" ;
my $ autoct = "automatic correction:" ;
my $ lbpcq = "correction quality current hour:" ;
my $ lblPv4h = "next 4h:" ;
my $ lblPvRe = "remain today:" ;
my $ lblPvTo = "tomorrow:" ;
my $ lblPvCu = "actual:" ;
if ( $ lang eq "DE" ) { # Header globales Sprachschema Deutsch
$ lupt = "Stand:" ;
$ autoct = "automatische Korrektur:" ;
$ lbpcq = encode ( "utf8" , "Korrekturqualität akt. Stunde:" ) ;
$ lblPv4h = encode ( "utf8" , "nächste 4h:" ) ;
$ lblPvRe = "Rest heute:" ;
$ lblPvTo = "morgen:" ;
$ lblPvCu = "aktuell:" ;
}
2021-06-01 14:49:10 +00:00
2021-06-10 20:32:37 +00:00
## Header Start
#################
$ header = qq{ <table width='100%'> } ;
# 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 ;
$ lup = "$year-$month-$day $time" ;
if ( $ lang eq "DE" ) {
$ lup = "$day.$month.$year $time" ;
}
my $ cmdupdate = qq{ "FW_cmd('$FW_ME$FW_subdir?XHR=1&cmd=get $name data')" } ; # Update Button generieren
if ( $ ftui eq "ftui" ) {
$ cmdupdate = qq{ "ftui.setFhemStatus('get $name data')" } ;
}
my $ upstate = ReadingsVal ( $ name , "state" , "" ) ;
## Update-Icon
##############
my ( $ upicon , $ img ) ;
if ( $ upstate =~ /updated|successfully|switched/ix ) {
$ img = FW_makeImage ( '10px-kreis-gruen.png' , $ htitles { upd } { $ lang } ) ;
$ upicon = "<a onClick=$cmdupdate>$img</a>" ;
}
elsif ( $ upstate =~ /running/ix ) {
$ img = FW_makeImage ( '10px-kreis-gelb.png' , 'running' ) ;
$ upicon = "<a>$img</a>" ;
}
elsif ( $ upstate =~ /initialized/ix ) {
$ img = FW_makeImage ( '1px-spacer.png' , 'initialized' ) ;
$ upicon = "<a>$img</a>" ;
}
else {
$ img = FW_makeImage ( '10px-kreis-rot.png' , $ htitles { upd } { $ lang } ) ;
$ upicon = "<a onClick=$cmdupdate>$img</a>" ;
}
## Autokorrektur-Icon
######################
my $ acicon ;
if ( $ pcfa eq "on" ) {
$ acicon = FW_makeImage ( '10px-kreis-gruen.png' , $ htitles { on } { $ lang } ) ;
}
elsif ( $ pcfa eq "off" ) {
$ acicon = "off" ;
}
elsif ( $ pcfa =~ /standby/ix ) {
my ( $ rtime ) = $ pcfa =~ /for (.*?) hours/x ;
$ img = FW_makeImage ( '10px-kreis-gelb.png' , $ htitles { dela } { $ lang } ) ;
$ acicon = "$img (Start in " . $ rtime . " h)" ;
}
else {
$ acicon = FW_makeImage ( '10px-kreis-rot.png' , $ htitles { undef } { $ lang } ) ;
}
## Qualitäts-Icon
######################
my $ pcqicon ;
$ pcqicon = $ pcq < 3 ? FW_makeImage ( '10px-kreis-rot.png' , $ pvcanz ) :
$ pcq < 5 ? FW_makeImage ( '10px-kreis-gelb.png' , $ pvcanz ) :
FW_makeImage ( '10px-kreis-gruen.png' , $ pvcanz ) ;
$ pcqicon = "-" if ( ! $ pvfc00 || $ pcq == - 1 ) ;
## erste Header-Zeilen
#######################
my $ alias = AttrVal ( $ name , "alias" , $ name ) ; # Linktext als Aliasname
my $ dlink = qq{ <a href="$FW_ME$FW_subdir?detail=$name">$alias</a> } ;
2020-12-13 17:29:15 +00:00
2021-06-10 20:32:37 +00:00
$ header . = qq{ <tr><td colspan="3" align="left" $dstyle><b> $dlink </b></td><td colspan="3" align="left" $dstyle> $lupt $lup $upicon </td><td> </td></tr> } ;
$ header . = qq{ <tr><td colspan="3" align="left" $dstyle><b> </b></td><td colspan="3" align="left" $dstyle> $autoct $acicon </td><td colspan="3" align="left" $dstyle> $lbpcq $pcqicon </td></tr> } ;
}
# Header Information pv
########################
if ( $ hdrDetail eq "all" || $ hdrDetail eq "pv" || $ hdrDetail eq "pvco" ) {
$ header . = "<tr>" ;
$ header . = "<td $dstyle><b>PV =></b></td>" ;
$ header . = "<td $dstyle><b>$lblPvCu</b></td> <td align=right $dstyle>$pvCu</td>" ;
$ header . = "<td $dstyle><b>$lblPv4h</b></td> <td align=right $dstyle>$pv4h</td>" ;
$ header . = "<td $dstyle><b>$lblPvRe</b></td> <td align=right $dstyle>$pvRe</td>" ;
$ header . = "<td $dstyle><b>$lblPvTo</b></td> <td align=right $dstyle>$pvTo</td>" ;
$ header . = "</tr>" ;
}
# Header Information co
########################
if ( $ hdrDetail eq "all" || $ hdrDetail eq "co" || $ hdrDetail eq "pvco" ) {
$ header . = "<tr>" ;
$ header . = "<td $dstyle><b>CO =></b></td>" ;
$ header . = "<td $dstyle><b>$lblPvCu</b></td><td align=right $dstyle>$coCu</td>" ;
$ header . = "<td $dstyle><b>$lblPv4h</b></td><td align=right $dstyle>$co4h</td>" ;
$ header . = "<td $dstyle><b>$lblPvRe</b></td><td align=right $dstyle>$coRe</td>" ;
$ header . = "<td $dstyle><b>$lblPvTo</b></td><td align=right $dstyle>$coTo</td>" ;
$ header . = "</tr>" ;
}
$ header . = qq{ </table> } ;
return $ header ;
}
2021-03-14 11:51:38 +00:00
2021-06-10 20:32:37 +00:00
################################################################
# Consumer in forecastGraphic (Balken) anzeigen
# (Hat zur Zeit keine Wirkung !)
################################################################
sub _showConsumerInGraphicBeam {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ hfcg = $ paref - > { hfcg } ;
my $ type = $ hash - > { TYPE } ;
2021-05-29 12:56:28 +00:00
# get consumer list and display it in Graphics
################################################
2021-06-10 20:32:37 +00:00
my @ consumers = sort { $ a <=> $ b } keys % { $ data { $ type } { $ name } { consumers } } ; # definierte Verbraucher ermitteln
2021-05-30 07:54:58 +00:00
for ( @ consumers ) {
2021-03-16 14:53:54 +00:00
my ( $ itemName , undef ) = split ( ':' , $ _ ) ;
2021-03-25 17:38:19 +00:00
$ 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
2021-05-26 20:47:27 +00:00
##################################
2021-03-16 14:53:54 +00:00
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
2021-05-26 20:47:27 +00:00
#######################################
2021-03-28 08:24:48 +00:00
if ( $ start < $ hfcg - > { 0 } { time } ) { # gridconsumption seems to be tomorrow
2021-03-21 21:21:16 +00:00
$ start = 24 - $ hfcg - > { 0 } { time } + $ start ;
2021-03-16 14:53:54 +00:00
$ flag = 1 ;
}
else {
2021-03-21 21:21:16 +00:00
$ start -= $ hfcg - > { 0 } { time } ;
2021-03-16 14:53:54 +00:00
}
2020-12-13 17:29:15 +00:00
2021-03-28 08:24:48 +00:00
if ( $ flag ) { # gridconsumption seems to be tomorrow
2021-03-21 21:21:16 +00:00
$ end = 24 - $ hfcg - > { 0 } { time } + $ end ;
2021-03-16 14:53:54 +00:00
}
else {
2021-03-21 21:21:16 +00:00
$ end -= $ hfcg - > { 0 } { time } ;
2021-03-16 14:53:54 +00:00
}
2020-12-13 17:29:15 +00:00
2021-03-16 14:53:54 +00:00
$ _ . = ":" . $ start . ":" . $ end ;
}
else {
$ _ . = ":24:24" ;
2021-05-29 12:56:28 +00:00
}
2020-12-13 17:29:15 +00:00
}
2021-06-10 20:32:37 +00:00
return ;
}
2020-12-13 17:29:15 +00:00
2021-06-10 20:32:37 +00:00
################################################################
# Verbraucherlegende und Steuerung
################################################################
sub _graphicConsumerLegend {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
2021-06-12 12:32:43 +00:00
my $ caicon = $ paref - > { caicon } ; # Consumer AdviceIcon
my ( $ clegendstyle , $ clegend ) = split ( '_' , $ paref - > { clegend } ) ;
2021-06-10 20:32:37 +00:00
my $ type = $ hash - > { TYPE } ;
my @ consumers = sort { $ a <=> $ b } keys % { $ data { $ type } { $ name } { consumers } } ; # definierte Verbraucher ermitteln
2021-06-12 12:32:43 +00:00
2021-06-10 20:32:37 +00:00
$ clegend = '' if ( ( $ clegendstyle eq 'none' ) || ( ! int ( @ consumers ) ) ) ;
$ paref - > { clegend } = $ clegend ;
return if ( ! $ clegend ) ;
my $ ftui = $ paref - > { ftui } ;
my $ lang = $ paref - > { lang } ;
my $ dstyle = $ paref - > { dstyle } ; # TD-Style
my $ staticon ;
## Tabelle Start
#################
my $ ctable = qq{ <table align='left' width='100%'> } ;
$ ctable . = qq{ <tr style='font-weight:bold; text-align:center'> } ;
2021-06-13 14:11:26 +00:00
$ ctable . = qq{ <td style='text-align:left' $dstyle> $hqtxt { cnsm } { $lang } </td> } ;
$ ctable . = qq{ <td> </td> } ;
$ ctable . = qq{ <td> </td> } ;
$ ctable . = qq{ <td $dstyle> $hqtxt { eiau } { $lang } </td> } ;
$ ctable . = qq{ <td $dstyle> $hqtxt { auto } { $lang } </td> } ;
2021-06-10 20:32:37 +00:00
my $ cnum = @ consumers ;
if ( $ cnum > 1 ) {
2021-06-13 14:11:26 +00:00
$ ctable . = qq{ <td style='text-align:left' $dstyle> $hqtxt { cnsm } { $lang } </td> } ;
$ ctable . = qq{ <td> </td> } ;
$ ctable . = qq{ <td> </td> } ;
$ ctable . = qq{ <td $dstyle> $hqtxt { eiau } { $lang } </td> } ;
$ ctable . = qq{ <td $dstyle> $hqtxt { auto } { $lang } </td> } ;
2021-06-10 20:32:37 +00:00
}
else {
my $ blk = ' ' x 8 ;
$ ctable . = qq{ <td $dstyle> $blk </td> } ;
$ ctable . = qq{ <td> $blk </td> } ;
2021-06-13 14:11:26 +00:00
$ ctable . = qq{ <td> $blk </td> } ;
2021-06-10 20:32:37 +00:00
$ ctable . = qq{ <td $dstyle> $blk </td> } ;
$ ctable . = qq{ <td $dstyle> $blk </td> } ;
}
$ ctable . = qq{ </tr> } ;
my $ modulo = 1 ;
my $ tro = 0 ;
for my $ c ( @ consumers ) {
2021-06-12 12:32:43 +00:00
my $ cname = ConsumerVal ( $ hash , $ c , "name" , "" ) ; # Name des Consumerdevices
my $ calias = ConsumerVal ( $ hash , $ c , "alias" , $ cname ) ; # Alias des Consumerdevices
my $ cicon = ConsumerVal ( $ hash , $ c , "icon" , "" ) ; # Icon des Consumerdevices
my $ oncom = ConsumerVal ( $ hash , $ c , "oncom" , "" ) ; # Consumer Einschaltkommando
my $ offcom = ConsumerVal ( $ hash , $ c , "offcom" , "" ) ; # Consumer Ausschaltkommando
my $ autord = ConsumerVal ( $ hash , $ c , "autoreading" , "" ) ; # Readingname f. Automatiksteuerung
my $ auto = ConsumerVal ( $ hash , $ c , "auto" , 1 ) ; # Automatic Mode
my $ iscrecomm = ConsumerVal ( $ hash , $ c , "isConsumptionRecommended" , 0 ) ; # ist einschalten Vervracher empfohlen
2021-06-10 20:32:37 +00:00
my $ cmdon = qq{ "FW_cmd('$FW_ME$FW_subdir?XHR=1&cmd=set $name consumerAction set $cname $oncom')" } ;
my $ cmdoff = qq{ "FW_cmd('$FW_ME$FW_subdir?XHR=1&cmd=set $name consumerAction set $cname $offcom')" } ;
my $ cmdautoon = qq{ "FW_cmd('$FW_ME$FW_subdir?XHR=1&cmd=set $name consumerAction setreading $cname $autord 1')" } ;
my $ cmdautooff = qq{ "FW_cmd('$FW_ME$FW_subdir?XHR=1&cmd=set $name consumerAction setreading $cname $autord 0')" } ;
if ( $ ftui eq "ftui" ) {
$ cmdon = qq{ "ftui.setFhemStatus('set $name consumerAction set $cname $oncom')" } ;
$ cmdoff = qq{ "ftui.setFhemStatus('set $name consumerAction set $cname $offcom')" } ;
$ cmdautoon = qq{ "ftui.setFhemStatus('set $name consumerAction set $cname setreading $cname $autord 1')" } ;
$ cmdautooff = qq{ "ftui.setFhemStatus('set $name consumerAction set $cname setreading $cname $autord 0')" } ;
}
$ cmdon = q{ } if ( ! $ oncom ) ;
$ cmdoff = q{ } if ( ! $ offcom ) ;
$ cmdautoon = q{ } if ( ! $ autord ) ;
$ cmdautooff = q{ } if ( ! $ autord ) ;
my $ swstate = ConsumerVal ( $ hash , $ c , "state" , "undef" ) ; # Schaltzustand des Consumerdevices
2021-06-12 12:32:43 +00:00
my $ swicon = q{ } ; # Schalter ein/aus Icon
my $ auicon = q{ } ; # Schalter Automatic Icon
my $ isricon = q{ } ; # Zustand IsRecommended Icon
2021-06-13 09:43:58 +00:00
2021-06-12 13:10:14 +00:00
$ paref - > { consumer } = $ c ;
my ( $ planstate , $ starttime , $ stoptime ) = __planningStateandTimes ( $ paref ) ;
2021-06-13 14:11:26 +00:00
my $ pstate = $ caicon eq "times" ? $ hqtxt { pstate } { $ lang } : $ htitles { pstate } { $ lang } ;
2021-06-12 13:10:14 +00:00
$ pstate =~ s/<pstate>/$planstate/xs ;
$ pstate =~ s/<start>/$starttime/xs ;
2021-06-13 14:11:26 +00:00
$ pstate =~ s/<stop>/$stoptime/xs ;
$ pstate =~ s/\s+/ /gxs if ( $ caicon eq "times" ) ;
2021-06-13 09:43:58 +00:00
if ( $ caicon ne "none" ) {
if ( $ iscrecomm ) {
2021-06-13 14:11:26 +00:00
if ( $ caicon eq "times" ) {
$ isricon = $ pstate ;
}
else {
$ isricon = "<a title= '$htitles{conrec}{$lang}\n\n$pstate'</a>" . FW_makeImage ( $ caicon , '' ) ;
}
2021-06-13 09:43:58 +00:00
}
else {
2021-06-13 14:11:26 +00:00
if ( $ caicon eq "times" ) {
$ isricon = $ pstate ;
}
else {
( $ caicon ) = split ( '\@' , $ caicon ) ;
$ isricon = "<a title= '$htitles{connorec}{$lang}\n\n$pstate'</a>" . FW_makeImage ( $ caicon . '@gray' , '' ) ;
}
2021-06-13 09:43:58 +00:00
}
}
2021-06-12 13:10:14 +00:00
2021-06-10 20:32:37 +00:00
if ( $ modulo % 2 ) {
$ ctable . = qq{ <tr> } ;
$ tro = 1 ;
}
if ( ! $ auto ) {
$ staticon = FW_makeImage ( 'ios_off_fill@red' , $ htitles { iaaf } { $ lang } ) ;
2021-06-13 09:43:58 +00:00
$ auicon = "<a title= '$htitles{iaaf}{$lang}' onClick=$cmdautoon> $staticon</a>" ;
2021-06-10 20:32:37 +00:00
}
if ( $ auto ) {
$ staticon = FW_makeImage ( 'ios_on_till_fill@orange' , $ htitles { ieas } { $ lang } ) ;
2021-06-13 09:43:58 +00:00
$ auicon = "<a title='$htitles{ieas}{$lang}' onClick=$cmdautooff> $staticon</a>" ;
2021-06-10 20:32:37 +00:00
}
2021-06-14 18:20:10 +00:00
if ( $ swstate eq "off" ) {
if ( $ cmdon ) {
$ staticon = FW_makeImage ( 'ios_off_fill@red' , $ htitles { iave } { $ lang } ) ;
$ swicon = "<a title='$htitles{iave}{$lang}' onClick=$cmdon> $staticon</a>" ;
}
else {
$ staticon = FW_makeImage ( 'ios_off_fill@grey' , $ htitles { ians } { $ lang } ) ;
$ swicon = "<a title='$htitles{ians}{$lang}'> $staticon</a>" ;
}
2021-06-10 20:32:37 +00:00
}
2021-06-14 18:20:10 +00:00
if ( $ swstate eq "on" ) {
if ( $ cmdoff ) {
$ staticon = FW_makeImage ( 'ios_on_fill@green' , $ htitles { ieva } { $ lang } ) ;
$ swicon = "<a title='$htitles{ieva}{$lang}' onClick=$cmdoff> $staticon</a>" ;
}
else {
$ staticon = FW_makeImage ( 'ios_on_fill@grey' , $ htitles { iens } { $ lang } ) ;
$ swicon = "<a title='$htitles{iens}{$lang}'> $staticon</a>" ;
}
2021-06-10 20:32:37 +00:00
}
if ( $ clegendstyle eq 'icon' ) {
$ cicon = FW_makeImage ( $ cicon ) ;
2021-06-12 12:32:43 +00:00
$ ctable . = "<td style='text-align:left' $dstyle>$calias </td>" ;
2021-06-13 14:11:26 +00:00
$ ctable . = "<td style='text-align:center' $dstyle>$cicon </td>" ;
$ ctable . = "<td style='text-align:center' $dstyle>$isricon </td>" ;
2021-06-12 12:32:43 +00:00
$ ctable . = "<td style='text-align:center' $dstyle>$swicon </td>" ;
$ ctable . = "<td style='text-align:center' $dstyle>$auicon </td>" ;
2021-06-10 20:32:37 +00:00
}
else {
2021-06-13 09:43:58 +00:00
my ( undef , $ co ) = split ( '\@' , $ cicon ) ;
2021-06-10 20:32:37 +00:00
$ co = '' if ( ! $ co ) ;
$ ctable . = "<td style='text-align:left' $dstyle><font color='$co'>$calias </font></td>" ;
2021-06-13 14:11:26 +00:00
$ ctable . = "<td> </td>" ;
2021-06-12 12:32:43 +00:00
$ ctable . = "<td> $isricon </td>" ;
2021-06-10 20:32:37 +00:00
$ ctable . = "<td style='text-align:center' $dstyle>$swicon </td>" ;
$ ctable . = "<td style='text-align:center' $dstyle>$auicon </td>" ;
}
if ( ! ( $ modulo % 2 ) ) {
$ ctable . = qq{ </tr> } ;
$ tro = 0 ;
}
$ modulo + + ;
}
2021-06-12 13:10:14 +00:00
delete $ paref - > { consumer } ;
2021-06-10 20:32:37 +00:00
$ ctable . = qq{ </tr> } if ( $ tro ) ;
$ ctable . = qq{ </table> } ;
return $ ctable ;
}
################################################################
# Werte aktuelle Stunde für forecastGraphic
################################################################
sub _beamGraphicFirstHour {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ hfcg = $ paref - > { hfcg } ;
my $ offset = $ paref - > { offset } ;
my $ hourstyle = $ paref - > { hourstyle } ;
my $ beam1cont = $ paref - > { beam1cont } ;
my $ beam2cont = $ paref - > { beam2cont } ;
my $ day ;
my $ t = NexthoursVal ( $ hash , "NextHour00" , "starttime" , '0000-00-00 24' ) ;
my ( $ year , $ month , $ day_str , $ thishour ) = $ t =~ m/(\d{4})-(\d{2})-(\d{2})\s(\d{2})/x ;
my ( $ val1 , $ val2 , $ val3 , $ val4 ) = ( 0 , 0 , 0 , 0 ) ;
$ thishour + + ;
$ hfcg - > { 0 } { time_str } = $ thishour ;
$ thishour = int ( $ thishour ) ; # keine führende Null
$ hfcg - > { 0 } { time } = $ thishour ;
$ hfcg - > { 0 } { day_str } = $ day_str ;
$ day = int ( $ day_str ) ;
$ hfcg - > { 0 } { day } = $ day ;
$ hfcg - > { 0 } { mktime } = fhemTimeLocal ( 0 , 0 , $ thishour , $ day , int ( $ month ) - 1 , $ year - 1900 ) ; # gleich die Unix Zeit dazu holen
if ( $ offset ) {
$ hfcg - > { 0 } { time } += $ offset ;
if ( $ hfcg - > { 0 } { time } < 0 ) {
$ hfcg - > { 0 } { time } += 24 ;
my $ n_day = strftime "%d" , localtime ( $ hfcg - > { 0 } { mktime } - ( 3600 * abs ( $ offset ) ) ) ; # Achtung : Tageswechsel - day muss jetzt neu berechnet werden !
$ hfcg - > { 0 } { day } = int ( $ n_day ) ;
$ hfcg - > { 0 } { day_str } = $ n_day ;
}
$ hfcg - > { 0 } { time_str } = sprintf ( '%02d' , $ hfcg - > { 0 } { time } ) ;
$ val1 = HistoryVal ( $ hash , $ hfcg - > { 0 } { day_str } , $ hfcg - > { 0 } { time_str } , "pvfc" , 0 ) ;
$ val2 = HistoryVal ( $ hash , $ hfcg - > { 0 } { day_str } , $ hfcg - > { 0 } { time_str } , "pvrl" , 0 ) ;
$ val3 = HistoryVal ( $ hash , $ hfcg - > { 0 } { day_str } , $ hfcg - > { 0 } { time_str } , "gcons" , 0 ) ;
$ val4 = HistoryVal ( $ hash , $ hfcg - > { 0 } { day_str } , $ hfcg - > { 0 } { time_str } , "confc" , 0 ) ;
# $hfcg->{0}{weather} = CircularVal ($hash, $hfcg->{0}{time_str}, "weatherid", undef);
$ hfcg - > { 0 } { weather } = HistoryVal ( $ hash , $ hfcg - > { 0 } { day_str } , $ hfcg - > { 0 } { time_str } , "weatherid" , undef ) ;
}
else {
$ val1 = CircularVal ( $ hash , $ hfcg - > { 0 } { time_str } , "pvfc" , 0 ) ;
$ val2 = CircularVal ( $ hash , $ hfcg - > { 0 } { time_str } , "pvrl" , 0 ) ;
$ val3 = CircularVal ( $ hash , $ hfcg - > { 0 } { time_str } , "gcons" , 0 ) ;
$ val4 = CircularVal ( $ hash , $ hfcg - > { 0 } { time_str } , "confc" , 0 ) ;
$ hfcg - > { 0 } { weather } = CircularVal ( $ hash , $ hfcg - > { 0 } { time_str } , "weatherid" , undef ) ;
#$val4 = (ReadingsVal($name,"ThisHour_IsConsumptionRecommended",'no') eq 'yes' ) ? $icon : undef;
}
$ hfcg - > { 0 } { time_str } = sprintf ( '%02d' , $ hfcg - > { 0 } { time } - 1 ) . $ hourstyle ;
$ hfcg - > { 0 } { beam1 } = ( $ beam1cont eq 'pvForecast' ) ? $ val1 : ( $ beam1cont eq 'pvReal' ) ? $ val2 : ( $ beam1cont eq 'gridconsumption' ) ? $ val3 : $ val4 ;
$ hfcg - > { 0 } { beam2 } = ( $ beam2cont eq 'pvForecast' ) ? $ val1 : ( $ beam2cont eq 'pvReal' ) ? $ val2 : ( $ beam2cont eq 'gridconsumption' ) ? $ val3 : $ val4 ;
$ hfcg - > { 0 } { diff } = $ hfcg - > { 0 } { beam1 } - $ hfcg - > { 0 } { beam2 } ;
return ( $ thishour ) ;
}
################################################################
# Werte restliche Stunden für forecastGraphic
################################################################
sub _beamGraphicRemainingHours {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ hfcg = $ paref - > { hfcg } ;
my $ offset = $ paref - > { offset } ;
my $ maxhours = $ paref - > { maxhours } ;
my $ hourstyle = $ paref - > { hourstyle } ;
my $ beam1cont = $ paref - > { beam1cont } ;
my $ beam2cont = $ paref - > { beam2cont } ;
my $ maxVal = $ paref - > { maxVal } ; # dyn. Anpassung der Balkenhöhe oder statisch ?
$ maxVal // = $ hfcg - > { 0 } { beam1 } ; # Startwert wenn kein Wert bereits via attr vorgegeben ist
my ( $ val1 , $ val2 , $ val3 , $ val4 ) ;
my $ maxCon = $ hfcg - > { 0 } { beam1 } ;
my $ maxDif = $ hfcg - > { 0 } { diff } ; # für Typ diff
my $ minDif = $ hfcg - > { 0 } { diff } ; # für Typ diff
for my $ i ( 1 .. ( $ maxhours * 2 ) - 1 ) { # doppelte Anzahl berechnen my $val1 = 0;
$ val2 = 0 ;
$ val3 = 0 ;
$ val4 = 0 ;
$ hfcg - > { $ i } { time } = $ hfcg - > { 0 } { time } + $ i ;
while ( $ hfcg - > { $ i } { time } > 24 ) {
$ hfcg - > { $ i } { time } -= 24 ; # wird bis zu 2x durchlaufen
}
$ hfcg - > { $ i } { time_str } = sprintf ( '%02d' , $ hfcg - > { $ i } { time } ) ;
my $ nh ; # next hour
if ( $ offset < 0 ) {
if ( $ i <= abs ( $ offset ) ) { # $daystr stimmt nur nach Mitternacht, vor Mitternacht muß $hfcg->{0}{day_str} als Basis verwendet werden !
my $ ds = strftime "%d" , localtime ( $ hfcg - > { 0 } { mktime } - ( 3600 * abs ( $ offset + $ i ) ) ) ; # V0.49.4
# Sonderfall Mitternacht
$ ds = strftime "%d" , localtime ( $ hfcg - > { 0 } { mktime } - ( 3600 * ( abs ( $ offset - $ i + 1 ) ) ) ) if ( $ hfcg - > { $ i } { time } == 24 ) ; # V0.49.4
2021-05-31 15:41:22 +00:00
$ val1 = HistoryVal ( $ hash , $ ds , $ hfcg - > { $ i } { time_str } , "pvfc" , 0 ) ;
$ val2 = HistoryVal ( $ hash , $ ds , $ hfcg - > { $ i } { time_str } , "pvrl" , 0 ) ;
$ val3 = HistoryVal ( $ hash , $ ds , $ hfcg - > { $ i } { time_str } , "gcons" , 0 ) ;
$ val4 = HistoryVal ( $ hash , $ ds , $ hfcg - > { $ i } { time_str } , "confc" , 0 ) ;
2021-05-12 11:12:59 +00:00
2021-03-23 22:01:47 +00:00
$ hfcg - > { $ i } { weather } = HistoryVal ( $ hash , $ ds , $ hfcg - > { $ i } { time_str } , "weatherid" , undef ) ;
2021-03-21 21:21:16 +00:00
}
else {
2021-04-14 12:18:26 +00:00
$ nh = sprintf ( '%02d' , $ i + $ offset ) ;
2021-03-21 21:21:16 +00:00
}
}
else {
$ nh = sprintf ( '%02d' , $ i ) ;
}
2020-12-13 17:29:15 +00:00
2021-03-21 21:21:16 +00:00
if ( defined ( $ nh ) ) {
2021-03-23 22:01:47 +00:00
$ val1 = NexthoursVal ( $ hash , 'NextHour' . $ nh , "pvforecast" , 0 ) ;
2021-05-12 11:12:59 +00:00
$ val4 = NexthoursVal ( $ hash , 'NextHour' . $ nh , "confc" , 0 ) ;
2021-03-23 22:01:47 +00:00
$ hfcg - > { $ i } { weather } = NexthoursVal ( $ hash , 'NextHour' . $ nh , "weatherid" , undef ) ;
2021-03-21 21:21:16 +00:00
#$val4 = (ReadingsVal($name,"NextHour".$ii."_IsConsumptionRecommended",'no') eq 'yes') ? $icon : undef;
}
2020-12-13 17:29:15 +00:00
2021-03-22 20:33:54 +00:00
$ hfcg - > { $ i } { time_str } = sprintf ( '%02d' , $ hfcg - > { $ i } { time } - 1 ) . $ hourstyle ;
2021-05-12 13:17:21 +00:00
$ hfcg - > { $ i } { beam1 } = ( $ beam1cont eq 'pvForecast' ) ? $ val1 : ( $ beam1cont eq 'pvReal' ) ? $ val2 : ( $ beam1cont eq 'gridconsumption' ) ? $ val3 : $ val4 ;
$ hfcg - > { $ i } { beam2 } = ( $ beam2cont eq 'pvForecast' ) ? $ val1 : ( $ beam2cont eq 'pvReal' ) ? $ val2 : ( $ beam2cont eq 'gridconsumption' ) ? $ val3 : $ val4 ;
2021-03-21 21:21:16 +00:00
# sicher stellen das wir keine undefs in der Liste haben !
$ hfcg - > { $ i } { beam1 } // = 0 ;
$ hfcg - > { $ i } { beam2 } // = 0 ;
$ hfcg - > { $ i } { diff } = $ hfcg - > { $ i } { beam1 } - $ hfcg - > { $ i } { beam2 } ;
$ maxVal = $ hfcg - > { $ i } { beam1 } if ( $ hfcg - > { $ i } { beam1 } > $ maxVal ) ;
$ maxCon = $ hfcg - > { $ i } { beam2 } if ( $ hfcg - > { $ i } { beam2 } > $ maxCon ) ;
2021-06-10 20:32:37 +00:00
$ maxDif = $ hfcg - > { $ i } { diff } if ( $ hfcg - > { $ i } { diff } > $ maxDif ) ;
$ minDif = $ hfcg - > { $ i } { diff } if ( $ hfcg - > { $ i } { diff } < $ minDif ) ;
2021-03-21 21:21:16 +00:00
}
2021-06-10 20:32:37 +00:00
my $ back = {
maxVal = > $ maxVal ,
maxCon = > $ maxCon ,
maxDif = > $ maxDif ,
minDif = > $ minDif ,
} ;
return ( $ back ) ;
}
################################################################
# Balkenausgabe für forecastGraphic
################################################################
sub _beamGraphic {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ hfcg = $ paref - > { hfcg } ;
my $ maxhours = $ paref - > { maxhours } ;
my $ weather = $ paref - > { weather } ;
my $ show_night = $ paref - > { show_night } ; # alle Balken (Spalten) anzeigen ?
my $ show_diff = $ paref - > { show_diff } ; # zusätzliche Anzeige $di{} in allen Typen
my $ colorw = $ paref - > { colorw } ; # Wetter Icon Farbe
my $ colorwn = $ paref - > { colorwn } ; # Wetter Icon Farbe Nacht
my $ lotype = $ paref - > { lotype } ;
my $ height = $ paref - > { height } ;
my $ fsize = $ paref - > { fsize } ;
my $ kw = $ paref - > { kw } ;
my $ width = $ paref - > { width } ;
my $ colorfc = $ paref - > { colorfc } ;
my $ colorc = $ paref - > { colorc } ;
my $ fcolor1 = $ paref - > { fcolor1 } ;
my $ fcolor2 = $ paref - > { fcolor2 } ;
my $ offset = $ paref - > { offset } ;
my $ thishour = $ paref - > { thishour } ;
2021-06-11 07:32:00 +00:00
my $ maxVal = $ paref - > { maxVal } ;
2021-06-10 20:32:37 +00:00
my $ maxCon = $ paref - > { maxCon } ;
my $ maxDif = $ paref - > { maxDif } ;
my $ minDif = $ paref - > { minDif } ;
my $ beam1cont = $ paref - > { beam1cont } ;
my $ beam2cont = $ paref - > { beam2cont } ;
2021-05-26 20:47:27 +00:00
2021-06-11 07:32:00 +00:00
$ lotype = 'single' if ( $ beam1cont eq $ beam2cont ) ; # User Auswahl Layout überschreiben bei gleichen Beamcontent !
2021-06-10 20:32:37 +00:00
2021-03-22 20:33:54 +00:00
# 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
2021-05-28 19:47:21 +00:00
my ( $ val , $ z2 , $ z3 , $ z4 , $ he ) ;
2021-06-10 20:32:37 +00:00
my $ ret ;
2021-06-11 07:32:00 +00:00
my $ m = $ paref - > { modulo } % 2 ;
2021-03-16 14:53:54 +00:00
2021-03-22 20:33:54 +00:00
if ( $ weather ) {
2021-06-11 07:32:00 +00:00
$ ret . = "<tr class='$htr{$m}{cl}'><td class='solarfc'></td>" ; # freier Platz am Anfang
2021-03-16 14:53:54 +00:00
2021-03-22 20:33:54 +00:00
my $ ii ;
for my $ i ( 0 .. ( $ maxhours * 2 ) - 1 ) {
last if ( ! exists ( $ hfcg - > { $ i } { weather } ) ) ;
2021-06-11 07:32:00 +00:00
next if ( ! $ show_night && defined ( $ hfcg - > { $ i } { weather } )
&& ( $ hfcg - > { $ i } { weather } > 99 )
&& ! $ hfcg - > { $ i } { beam1 }
&& ! $ hfcg - > { $ i } { beam2 } ) ;
# Lässt Nachticons aber noch durch wenn es einen Wert gibt , ToDo : klären ob die Nacht richtig gesetzt wurde
$ ii + + ; # wieviele Stunden Icons haben wir bisher beechnet ?
2021-03-26 12:03:05 +00:00
last if ( $ ii > $ maxhours ) ;
2021-06-11 07:32:00 +00:00
# ToDo : weather_icon sollte im Fehlerfall Title mit der ID besetzen um in FHEMWEB sofort die ID sehen zu können
2021-03-22 20:33:54 +00:00
if ( exists ( $ hfcg - > { $ i } { weather } ) && defined ( $ hfcg - > { $ i } { weather } ) ) {
2021-06-12 07:03:47 +00:00
my ( $ icon_name , $ title ) = $ hfcg - > { $ i } { weather } > 100 ?
weather_icon ( $ hfcg - > { $ i } { weather } - 100 ) :
weather_icon ( $ hfcg - > { $ i } { weather } ) ;
if ( $ icon_name eq 'unknown' ) {
Log3 ( $ name , 4 , "$name - unknown weather id: " . $ hfcg - > { $ i } { weather } . ", please inform the maintainer" ) ;
}
2021-06-11 07:32:00 +00:00
2021-03-26 12:03:05 +00:00
$ icon_name . = ( $ hfcg - > { $ i } { weather } < 100 ) ? '@' . $ colorw : '@' . $ colorwn ;
$ val = FW_makeImage ( $ icon_name ) ;
2020-12-13 17:29:15 +00:00
2021-06-11 07:32:00 +00:00
if ( $ val eq $ icon_name ) { # passendes Icon beim User nicht vorhanden ! ( attr web iconPath falsch/prüfen/update ? )
2021-04-14 12:18:26 +00:00
$ val = '<b>???<b/>' ;
2021-05-15 07:36:53 +00:00
Log3 ( $ name , 2 , qq{ $name - the icon $hfcg-> { $i } { weather } not found. Please check attribute "iconPath" of your FHEMWEB instance and/or update your FHEM software } ) ;
2021-03-26 12:03:05 +00:00
}
2021-04-14 12:18:26 +00:00
2021-06-11 07:32:00 +00:00
$ ret . = "<td title='$title' class='solarfc' width='$width' style='margin:1px; vertical-align:middle align:center; padding-bottom:1px;'>$val</td>" ;
2021-03-22 20:33:54 +00:00
}
2021-06-11 07:32:00 +00:00
else { # mit $hfcg->{$i}{weather} = undef kann man unten leicht feststellen ob für diese Spalte bereits ein Icon ausgegeben wurde oder nicht
2021-03-22 20:33:54 +00:00
$ ret . = "<td></td>" ;
2021-06-11 07:32:00 +00:00
$ hfcg - > { $ i } { weather } = undef ; # ToDo : prüfen ob noch nötig
2021-03-22 20:33:54 +00:00
}
}
2020-12-13 17:29:15 +00:00
2021-06-11 07:32:00 +00:00
$ ret . = "<td class='solarfc'></td></tr>" ; # freier Platz am Ende der Icon Zeile
2021-03-22 20:33:54 +00:00
}
2020-12-13 17:29:15 +00:00
2021-06-11 07:32:00 +00:00
if ( $ show_diff eq 'top' ) { # Zusätzliche Zeile Ertrag - Verbrauch
$ ret . = "<tr class='$htr{$m}{cl}'><td class='solarfc'></td>" ;
2021-03-22 20:33:54 +00:00
my $ ii ;
2021-06-11 07:32:00 +00:00
for my $ i ( 0 .. ( $ maxhours * 2 ) - 1 ) { # gleiche Bedingung wie oben
next if ( ! $ show_night && ( $ hfcg - > { $ i } { weather } > 99 )
&& ! $ hfcg - > { $ i } { beam1 }
&& ! $ hfcg - > { $ i } { beam2 } ) ;
$ ii + + ; # wieviele Stunden haben wir bisher angezeigt ?
last if ( $ ii > $ maxhours ) ; # vorzeitiger Abbruch
2020-12-13 17:29:15 +00:00
2021-03-22 20:33:54 +00:00
$ val = formatVal6 ( $ hfcg - > { $ i } { diff } , $ kw , $ hfcg - > { $ i } { weather } ) ;
2021-06-12 07:03:47 +00:00
$ val = $ hfcg - > { $ i } { diff } < 0 ?
'<b>' . $ val . '<b/>' :
$ val > 0 ?
'+' . $ val :
$ val ; # negative Zahlen in Fettschrift, 0 aber ohne +
2021-05-28 19:47:21 +00:00
$ ret . = "<td class='solarfc' style='vertical-align:middle; text-align:center;'>$val</td>" ;
2021-03-22 20:33:54 +00:00
}
2021-06-11 07:32:00 +00:00
$ ret . = "<td class='solarfc'></td></tr>" ; # freier Platz am Ende
2021-03-22 20:33:54 +00:00
}
2020-12-13 17:29:15 +00:00
2021-06-11 07:32:00 +00:00
$ ret . = "<tr class='$htr{$m}{cl}'><td class='solarfc'></td>" ; # Neue Zeile mit freiem Platz am Anfang
2020-12-13 17:29:15 +00:00
2021-03-22 20:33:54 +00:00
my $ ii = 0 ;
2021-05-31 15:41:22 +00:00
2021-06-11 07:32:00 +00:00
for my $ i ( 0 .. ( $ maxhours * 2 ) - 1 ) { # gleiche Bedingung wie oben
next if ( ! $ show_night && defined ( $ hfcg - > { $ i } { weather } )
&& ( $ hfcg - > { $ i } { weather } > 99 )
&& ! $ hfcg - > { $ i } { beam1 }
&& ! $ hfcg - > { $ i } { beam2 } ) ;
2021-03-22 20:33:54 +00:00
$ ii + + ;
last if ( $ ii > $ maxhours ) ;
2020-12-13 17:29:15 +00:00
2021-03-22 20:33:54 +00:00
# Achtung Falle, Division by Zero möglich,
# maxVal kann gerade bei kleineren maxhours Ausgaben in der Nacht leicht auf 0 fallen
2021-06-11 07:32:00 +00:00
$ height = 200 if ( ! $ height ) ; # Fallback, sollte eigentlich nicht vorkommen, außer der User setzt es auf 0
2021-03-22 20:33:54 +00:00
$ maxVal = 1 if ( ! int $ maxVal ) ;
$ maxCon = 1 if ( ! $ maxCon ) ;
2020-12-13 17:29:15 +00:00
2021-03-22 20:33:54 +00:00
# Der zusätzliche Offset durch $fsize verhindert bei den meisten Skins
# dass die Grundlinie der Balken nach unten durchbrochen wird
2021-03-25 17:38:19 +00:00
if ( $ lotype eq 'single' ) {
2021-03-22 20:33:54 +00:00
$ he = int ( ( $ maxVal - $ hfcg - > { $ i } { beam1 } ) / $ maxVal * $ height ) + $ fsize ;
$ z3 = int ( $ height + $ fsize - $ he ) ;
}
2020-12-13 17:29:15 +00:00
2021-03-25 17:38:19 +00:00
if ( $ lotype eq 'double' ) {
2021-03-22 20:33:54 +00:00
# Berechnung der Zonen
# he - freier der Raum über den Balken. fsize wird nicht verwendet, da bei diesem Typ keine Zahlen über den Balken stehen
# z2 - der Ertrag ggf mit Icon
# z3 - der Verbrauch , bei zu kleinem Wert wird der Platz komplett Zone 2 zugeschlagen und nicht angezeigt
# z2 und z3 nach Bedarf tauschen, wenn der Verbrauch größer als der Ertrag ist
2020-12-13 17:29:15 +00:00
2021-06-11 07:32:00 +00:00
$ maxVal = $ maxCon if ( $ maxCon > $ maxVal ) ; # wer hat den größten Wert ?
2020-12-13 17:29:15 +00:00
2021-06-11 07:32:00 +00:00
if ( $ hfcg - > { $ i } { beam1 } > $ hfcg - > { $ i } { beam2 } ) { # Beam1 oben , Beam2 unten
2021-03-22 20:33:54 +00:00
$ z2 = $ hfcg - > { $ i } { beam1 } ; $ z3 = $ hfcg - > { $ i } { beam2 } ;
}
2021-06-11 07:32:00 +00:00
else { # tauschen, Verbrauch ist größer als Ertrag
2021-03-22 20:33:54 +00:00
$ z3 = $ hfcg - > { $ i } { beam1 } ; $ z2 = $ hfcg - > { $ i } { beam2 } ;
}
$ he = int ( ( $ maxVal - $ z2 ) / $ maxVal * $ height ) ;
$ z2 = int ( ( $ z2 - $ z3 ) / $ maxVal * $ height ) ;
2021-06-11 07:32:00 +00:00
$ z3 = int ( $ height - $ he - $ z2 ) ; # was von maxVal noch übrig ist
2020-12-13 17:29:15 +00:00
2021-06-11 07:32:00 +00:00
if ( $ z3 < int ( $ fsize /2)) { # dünnen Strichbalken vermeiden / ca . halbe Zeichenhöhe
2021-06-12 07:03:47 +00:00
$ z2 += $ z3 ;
$ z3 = 0 ;
2021-03-22 20:33:54 +00:00
}
}
2020-12-13 17:29:15 +00:00
2021-06-11 07:32:00 +00:00
if ( $ lotype eq 'diff' ) {
2021-03-22 20:33:54 +00:00
# Berechnung der Zonen
# he - freier der Raum über den Balken , Zahl positiver Wert + fsize
# z2 - positiver Balken inkl Icon
# z3 - negativer Balken
# z4 - Zahl negativer Wert + fsize
2020-12-13 17:29:15 +00:00
2021-03-22 20:33:54 +00:00
my ( $ px_pos , $ px_neg ) ;
2021-06-11 07:32:00 +00:00
my $ maxValBeam = 0 ; # ToDo: maxValBeam noch aus Attribut maxValBeam ableiten
2021-03-14 11:51:38 +00:00
2021-06-11 07:32:00 +00:00
if ( $ maxValBeam ) { # Feste Aufteilung +/- , jeder 50 % bei maxValBeam = 0
2021-03-22 20:33:54 +00:00
$ px_pos = int ( $ height / 2 ) ;
2021-06-11 07:32:00 +00:00
$ px_neg = $ height - $ px_pos ; # Rundungsfehler vermeiden
2021-03-22 20:33:54 +00:00
}
2021-06-11 07:32:00 +00:00
else { # Dynamische hoch/runter Verschiebung der Null-Linie
if ( $ minDif >= 0 ) { # keine negativen Balken vorhanden, die Positiven bekommen den gesammten Raum
2021-03-22 20:33:54 +00:00
$ px_neg = 0 ;
$ px_pos = $ height ;
}
else {
if ( $ maxDif > 0 ) {
2021-06-11 07:32:00 +00:00
$ px_neg = int ( $ height * abs ( $ minDif ) / ( $ maxDif + abs ( $ minDif ) ) ) ; # Wieviel % entfallen auf unten ?
$ px_pos = $ height - $ px_neg ; # der Rest ist oben
2021-03-22 20:33:54 +00:00
}
2021-06-11 07:32:00 +00:00
else { # keine positiven Balken vorhanden, die Negativen bekommen den gesammten Raum
2021-03-22 20:33:54 +00:00
$ px_neg = $ height ;
$ px_pos = 0 ;
}
}
}
2021-03-14 11:51:38 +00:00
2021-06-11 07:32:00 +00:00
if ( $ hfcg - > { $ i } { diff } >= 0 ) { # Zone 2 & 3 mit ihren direkten Werten vorbesetzen
2021-03-22 20:33:54 +00:00
$ z2 = $ hfcg - > { $ i } { diff } ;
$ z3 = abs ( $ minDif ) ;
}
else {
$ z2 = $ maxDif ;
2021-06-11 07:32:00 +00:00
$ z3 = abs ( $ hfcg - > { $ i } { diff } ) ; # Nur Betrag ohne Vorzeichen
2021-03-22 20:33:54 +00:00
}
2021-06-11 07:32:00 +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
2021-03-22 20:33:54 +00:00
$ z2 = ( $ px_pos - $ he ) ;
2020-12-13 17:29:15 +00:00
2021-06-11 07:32:00 +00:00
$ z4 = ( ! $ px_neg || ! $ minDif ) ? 0 : int ( ( abs ( $ minDif ) - $ z3 ) / abs ( $ minDif ) * $ px_neg ) ; # Teilung durch 0 unbedingt vermeiden
2021-03-22 20:33:54 +00:00
$ z3 = ( $ px_neg - $ z4 ) ;
2021-06-11 07:32:00 +00:00
# Beiden Zonen die Werte ausgeben könnten muß fsize als zusätzlicher Raum zugeschlagen werden !
2021-03-22 20:33:54 +00:00
$ he += $ fsize ;
2021-06-11 07:32:00 +00:00
$ z4 += $ fsize if ( $ z3 ) ; # komplette Grafik ohne negativ Balken, keine Ausgabe von z3 & z4
2021-03-22 20:33:54 +00:00
}
# das style des nächsten TD bestimmt ganz wesentlich das gesammte Design
# das \n erleichtert das lesen des Seitenquelltext beim debugging
# vertical-align:bottom damit alle Balken und Ausgaben wirklich auf der gleichen Grundlinie sitzen
$ ret . = "<td style='text-align: center; padding-left:1px; padding-right:1px; margin:0px; vertical-align:bottom; padding-top:0px'>\n" ;
2021-03-25 17:38:19 +00:00
if ( $ lotype eq 'single' ) {
2021-03-22 20:33:54 +00:00
$ val = formatVal6 ( $ hfcg - > { $ i } { beam1 } , $ kw , $ hfcg - > { $ i } { weather } ) ;
2021-06-11 07:32:00 +00:00
$ ret . = "<table width='100%' height='100%'>" ; # mit width=100% etwas bessere Füllung der Balken
$ ret . = "<tr class='$htr{$m}{cl}' style='height:" . $ he . "px'>" ;
2021-05-28 19:47:21 +00:00
$ ret . = "<td class='solarfc' style='vertical-align:bottom; color:#$fcolor1;'>" . $ val . '</td></tr>' ;
2021-03-22 20:33:54 +00:00
2021-06-11 07:32:00 +00:00
if ( $ hfcg - > { $ i } { beam1 } || $ show_night ) { # Balken nur einfärben wenn der User via Attr eine Farbe vorgibt, sonst bestimmt class odd von TR alleine die Farbe
2021-03-22 20:33:54 +00:00
my $ style = "style=\"padding-bottom:0px; vertical-align:top; margin-left:auto; margin-right:auto;" ;
2021-06-11 07:32:00 +00:00
$ style . = defined $ colorfc ? " background-color:#$colorfc\"" : '"' ; # Syntaxhilight
2021-03-22 20:33:54 +00:00
$ ret . = "<tr class='odd' style='height:" . $ z3 . "px;'>" ;
2021-05-28 19:47:21 +00:00
$ ret . = "<td align='center' class='solarfc' " . $ style . ">" ;
2021-03-22 20:33:54 +00:00
my $ sicon = 1 ;
#$ret .= $is{$i} if (defined ($is{$i}) && $sicon);
# inject the new icon if defined
2021-05-26 20:47:27 +00:00
##################################
2021-05-30 07:54:58 +00:00
#$ret .= consinject($hash,$i,@consumers) if($s);
2021-03-22 20:33:54 +00:00
$ ret . = "</td></tr>" ;
}
}
2021-04-03 06:44:58 +00:00
if ( $ lotype eq 'double' ) {
2021-03-22 20:33:54 +00:00
my ( $ color1 , $ color2 , $ style1 , $ style2 , $ v ) ;
2021-04-03 06:44:58 +00:00
my $ style = "style='padding-bottom:0px; padding-top:1px; vertical-align:top; margin-left:auto; margin-right:auto;" ;
2021-03-22 20:33:54 +00:00
$ ret . = "<table width='100%' height='100%'>\n" ; # mit width=100% etwas bessere Füllung der Balken
2021-06-11 07:32:00 +00:00
# der Freiraum oben kann beim größten Balken ganz entfallen
$ ret . = "<tr class='$htr{$m}{cl}' style='height:" . $ he . "px'><td class='solarfc'></td></tr>" if ( $ he ) ;
2021-03-22 20:33:54 +00:00
2021-03-25 17:38:19 +00:00
if ( $ hfcg - > { $ i } { beam1 } > $ hfcg - > { $ i } { beam2 } ) { # wer ist oben, Beam2 oder Beam1 ? Wert und Farbe für Zone 2 & 3 vorbesetzen
2021-03-22 20:33:54 +00:00
$ val = formatVal6 ( $ hfcg - > { $ i } { beam1 } , $ kw , $ hfcg - > { $ i } { weather } ) ;
$ color1 = $ colorfc ;
2021-04-03 06:44:58 +00:00
$ style1 = $ style . " background-color:#$color1; color:#$fcolor1;'" ;
2021-03-22 20:33:54 +00:00
if ( $ z3 ) { # die Zuweisung können wir uns sparen wenn Zone 3 nachher eh nicht ausgegeben wird
$ v = formatVal6 ( $ hfcg - > { $ i } { beam2 } , $ kw , $ hfcg - > { $ i } { weather } ) ;
$ color2 = $ colorc ;
2021-04-03 06:44:58 +00:00
$ style2 = $ style . " background-color:#$color2; color:#$fcolor2;'" ;
}
}
2021-03-22 20:33:54 +00:00
else {
$ val = formatVal6 ( $ hfcg - > { $ i } { beam2 } , $ kw , $ hfcg - > { $ i } { weather } ) ;
$ color1 = $ colorc ;
2021-04-03 06:44:58 +00:00
$ style1 = $ style . " background-color:#$color1; color:#$fcolor2;'" ;
2021-03-22 20:33:54 +00:00
if ( $ z3 ) {
$ v = formatVal6 ( $ hfcg - > { $ i } { beam1 } , $ kw , $ hfcg - > { $ i } { weather } ) ;
$ color2 = $ colorfc ;
2021-04-03 06:44:58 +00:00
$ style2 = $ style . " background-color:#$color2; color:#$fcolor1;'" ;
2021-03-22 20:33:54 +00:00
}
}
2020-12-13 17:29:15 +00:00
2021-03-22 20:33:54 +00:00
$ ret . = "<tr class='odd' style='height:" . $ z2 . "px'>" ;
2021-05-28 19:47:21 +00:00
$ ret . = "<td align='center' class='solarfc' " . $ style1 . ">$val" ;
2021-03-22 20:33:54 +00:00
# inject the new icon if defined
2021-05-26 20:47:27 +00:00
##################################
2021-05-30 07:54:58 +00:00
#$ret .= consinject($hash,$i,@consumers) if($s);
2021-03-22 20:33:54 +00:00
$ ret . = "</td></tr>" ;
2021-06-11 07:32:00 +00:00
if ( $ z3 ) { # die Zone 3 lassen wir bei zu kleinen Werten auch ganz weg
2021-03-22 20:33:54 +00:00
$ ret . = "<tr class='odd' style='height:" . $ z3 . "px'>" ;
2021-05-28 19:47:21 +00:00
$ ret . = "<td align='center' class='solarfc' " . $ style2 . ">$v</td></tr>" ;
2021-03-22 20:33:54 +00:00
}
2021-04-03 06:44:58 +00:00
}
2020-12-13 17:29:15 +00:00
2021-06-11 07:32:00 +00:00
if ( $ lotype eq 'diff' ) { # Type diff
2021-04-03 06:44:58 +00:00
my $ style = "style='padding-bottom:0px; padding-top:1px; vertical-align:top; margin-left:auto; margin-right:auto;" ;
2021-06-11 07:32:00 +00:00
$ ret . = "<table width='100%' border='0'>\n" ; # Tipp : das nachfolgende border=0 auf 1 setzen hilft sehr Ausgabefehler zu endecken
2020-12-13 17:29:15 +00:00
2021-03-22 20:33:54 +00:00
$ val = ( $ hfcg - > { $ i } { diff } > 0 ) ? formatVal6 ( $ hfcg - > { $ i } { diff } , $ kw , $ hfcg - > { $ i } { weather } ) : '' ;
2021-06-11 07:32:00 +00:00
$ val = ' 0 ' if ( $ hfcg - > { $ i } { diff } == 0 ) ; # Sonderfall , hier wird die 0 gebraucht !
2020-12-13 17:29:15 +00:00
2021-03-22 20:33:54 +00:00
if ( $ val ) {
2021-06-11 07:32:00 +00:00
$ ret . = "<tr class='$htr{$m}{cl}' style='height:" . $ he . "px'>" ;
2021-05-28 19:47:21 +00:00
$ ret . = "<td class='solarfc' style='vertical-align:bottom; color:#$fcolor1;'>" . $ val . "</td></tr>" ;
2021-03-22 20:33:54 +00:00
}
2020-12-13 17:29:15 +00:00
2021-06-11 07:32:00 +00:00
if ( $ hfcg - > { $ i } { diff } >= 0 ) { # mit Farbe 1 colorfc füllen
2021-04-03 06:44:58 +00:00
$ style . = " background-color:#$colorfc'" ;
2021-06-11 07:32:00 +00:00
$ z2 = 1 if ( $ hfcg - > { $ i } { diff } == 0 ) ; # Sonderfall , 1px dünnen Strich ausgeben
2021-05-31 15:41:22 +00:00
$ ret . = "<tr class='odd' style='height:" . $ z2 . "px'>" ;
$ ret . = "<td align='center' class='solarfc' " . $ style . ">" ;
$ ret . = "</td></tr>" ;
2021-03-22 20:33:54 +00:00
}
2021-06-11 07:32:00 +00:00
else { # ohne Farbe
$ z2 = 2 if ( $ hfcg - > { $ i } { diff } == 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='$htr{$m}{cl}' style='height:" . $ z2 . "px'>" ;
2021-05-28 19:47:21 +00:00
$ ret . = "<td class='solarfc'></td></tr>" ;
2021-03-22 20:33:54 +00:00
}
}
2021-04-03 06:44:58 +00:00
2021-06-11 07:32:00 +00:00
if ( $ hfcg - > { $ i } { diff } < 0 ) { # Negativ Balken anzeigen ?
$ style . = " background-color:#$colorc'" ; # mit Farbe 2 colorc füllen
2021-03-22 20:33:54 +00:00
$ ret . = "<tr class='odd' style='height:" . $ z3 . "px'>" ;
2021-05-28 19:47:21 +00:00
$ ret . = "<td align='center' class='solarfc' " . $ style . "></td></tr>" ;
2021-04-03 06:44:58 +00:00
}
2021-06-11 07:32:00 +00:00
elsif ( $ z3 ) { # ohne Farbe
$ ret . = "<tr class='$htr{$m}{cl}' style='height:" . $ z3 . "px'>" ;
2021-05-28 19:47:21 +00:00
$ ret . = "<td class='solarfc'></td></tr>" ;
2021-03-22 20:33:54 +00:00
}
2021-03-16 14:53:54 +00:00
2021-06-11 07:32:00 +00:00
if ( $ z4 ) { # kann entfallen wenn auch z3 0 ist
2021-03-22 20:33:54 +00:00
$ val = ( $ hfcg - > { $ i } { diff } < 0 ) ? formatVal6 ( $ hfcg - > { $ i } { diff } , $ kw , $ hfcg - > { $ i } { weather } ) : ' ' ;
2021-06-11 07:32:00 +00:00
$ ret . = "<tr class='$htr{$m}{cl}' style='height:" . $ z4 . "px'>" ;
2021-05-28 19:47:21 +00:00
$ ret . = "<td class='solarfc' style='vertical-align:top'>" . $ val . "</td></tr>" ;
2021-03-22 20:33:54 +00:00
}
}
2021-06-11 07:32:00 +00:00
if ( $ show_diff eq 'bottom' ) { # zusätzliche diff Anzeige
2021-03-22 20:33:54 +00:00
$ val = formatVal6 ( $ hfcg - > { $ i } { diff } , $ kw , $ hfcg - > { $ i } { weather } ) ;
$ val = ( $ hfcg - > { $ i } { diff } < 0 ) ? '<b>' . $ val . '<b/>' : ( $ val > 0 ) ? '+' . $ val : $ val if ( $ val ne ' ' ) ; # negative Zahlen in Fettschrift, 0 aber ohne +
2021-06-11 07:32:00 +00:00
$ ret . = "<tr class='$htr{$m}{cl}'><td class='solarfc' style='vertical-align:middle; text-align:center;'>$val</td></tr>" ;
2021-03-22 20:33:54 +00:00
}
2021-03-16 14:53:54 +00:00
2021-06-11 07:32:00 +00:00
$ ret . = "<tr class='$htr{$m}{cl}'><td class='solarfc' style='vertical-align:bottom; text-align:center;'>" ;
$ ret . = ( ( $ hfcg - > { $ i } { time } == $ thishour ) && ( $ offset < 0 ) ) ? '<a class="changed" style="visibility:visible"><span>' . $ hfcg - > { $ i } { time_str } . '</span></a>' : $ hfcg - > { $ i } { time_str } ;
2021-06-10 20:32:37 +00:00
if ( $ hfcg - > { $ i } { time } == $ thishour ) {
2021-06-11 07:32:00 +00:00
$ thishour = 99 ; # nur einmal verwenden !
2021-06-10 20:32:37 +00:00
}
$ ret . = "</td></tr></table></td>" ;
2021-03-22 20:33:54 +00:00
}
2021-06-11 07:32:00 +00:00
$ paref - > { modulo } + + ;
2021-03-16 14:53:54 +00:00
2021-06-10 20:32:37 +00:00
$ ret . = "<td class='solarfc'></td>" ;
$ ret . = "</tr>" ;
2021-03-22 20:33:54 +00:00
return $ ret ;
2020-12-13 17:29:15 +00:00
}
2021-05-29 12:56:28 +00:00
################################################################
2021-06-10 20:32:37 +00:00
# Energieflußgrafik
2021-05-29 12:56:28 +00:00
################################################################
2021-06-10 20:32:37 +00:00
sub _flowGraphic {
2021-06-12 07:03:47 +00:00
my $ paref = shift ;
my $ name = $ paref - > { name } ;
my $ flowgh = $ paref - > { flowgh } ;
my $ flowgani = $ paref - > { flowgani } ;
2021-06-12 09:24:58 +00:00
my $ css = $ paref - > { css } ;
2021-05-26 20:47:27 +00:00
2021-05-29 12:56:28 +00:00
my $ style = 'width:' . $ flowgh . 'px; height:' . $ flowgh . 'px;' ;
2021-06-12 07:03:47 +00:00
my $ fs = $ flowgh < 300 ? '48px' : '32px' ;
2021-05-29 12:56:28 +00:00
my $ animation = $ flowgani ? '@keyframes dash { to { stroke-dashoffset: 0; } }' : '' ; # Animation Ja/Nein
2021-05-27 20:23:40 +00:00
2021-05-29 12:56:28 +00:00
my $ cpv = ReadingsNum ( $ name , 'Current_PV' , 0 ) ;
2021-06-12 07:03:47 +00:00
my $ sun_color = $ cpv ? 'flowg sun_active' : 'flowg sun_inactive' ;
2021-05-27 20:23:40 +00:00
2021-05-29 12:56:28 +00:00
my $ cgc = ReadingsNum ( $ name , 'Current_GridConsumption' , 0 ) ;
2021-06-12 07:03:47 +00:00
my $ cgc_style = $ cgc ? 'flowg active_in' : 'flowg inactive_in' ;
2021-05-27 20:23:40 +00:00
2021-05-29 12:56:28 +00:00
my $ cgfi = ReadingsNum ( $ name , 'Current_GridFeedIn' , 0 ) ;
2021-06-12 07:03:47 +00:00
my $ cgfi_style = $ cgfi ? 'flowg active_out' : 'flowg inactive_out' ;
2021-05-27 20:23:40 +00:00
2021-05-29 12:56:28 +00:00
my $ csc = ReadingsNum ( $ name , 'Current_SelfConsumption' , 0 ) ;
2021-06-12 07:03:47 +00:00
my $ csc_style = $ csc ? 'flowg active_out' : 'flowg inactive_out' ;
2021-05-27 20:23:40 +00:00
2021-05-29 12:56:28 +00:00
my $ batin = ReadingsNum ( $ name , 'Current_PowerBatIn' , undef ) ;
my $ batout = ReadingsNum ( $ name , 'Current_PowerBatOut' , undef ) ;
2021-06-12 07:03:47 +00:00
my $ soc = ReadingsNum ( $ name , 'Current_BatCharge' , 100 ) ;
2021-06-03 08:21:24 +00:00
2021-06-12 07:03:47 +00:00
my $ bat_color = $ soc < 26 ? 'flowg bat25' :
$ soc < 76 ? 'flowg bat50' :
'flowg bat75' ;
2021-05-29 12:56:28 +00:00
my $ hasbat = 1 ;
2021-05-27 20:23:40 +00:00
if ( ! defined ( $ batin ) && ! defined ( $ batout ) ) {
$ hasbat = 0 ;
$ batin = 0 ;
$ batout = 0 ;
$ soc = 0 ;
}
2021-06-12 07:03:47 +00:00
my $ batin_style = $ batin ? 'flowg active_out' : 'flowg inactive_out' ;
my $ batout_style = $ batout ? 'flowg active_in' : 'flowg inactive_in' ;
my $ grid_color = $ cgfi ? 'flowg grid_color1' : 'flowg grid_color2' ;
$ grid_color = 'flowg grid_color3' if ( ! $ cgfi && ! $ cgc && $ batout ) ; # dritte Farbe
2021-06-10 20:32:37 +00:00
my $ ret ;
2021-06-12 07:03:47 +00:00
2021-06-10 20:32:37 +00:00
$ ret . = qq{
2021-06-12 07:03:47 +00:00
<style>
$ css
$ animation
</style>
2021-05-30 07:54:58 +00:00
< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "5 15 380 380" style = "$style" id = "SVGPLOT" >
2021-06-12 07:03:47 +00:00
2021-05-27 20:23:40 +00:00
< g transform = "translate(200,50)" >
<g>
2021-06-12 07:03:47 +00:00
< line class = "$sun_color" stroke - linecap = "round" stroke - width = "5" transform = "translate(0,9)" x1 = "0" x2 = "0" y1 = "16" y2 = "24" / >
2021-05-26 20:47:27 +00:00
</g>
2021-05-27 20:23:40 +00:00
< g transform = "rotate(45)" >
2021-06-12 07:03:47 +00:00
< line class = "$sun_color" stroke - linecap = "round" stroke - width = "5" transform = "translate(0,9)" x1 = "0" x2 = "0" y1 = "16" y2 = "24" / >
2021-05-26 20:47:27 +00:00
</g>
2021-05-27 20:23:40 +00:00
< g transform = "rotate(90)" >
2021-06-12 07:03:47 +00:00
< line class = "$sun_color" stroke - linecap = "round" stroke - width = "5" transform = "translate(0,9)" x1 = "0" x2 = "0" y1 = "16" y2 = "24" / >
2021-05-27 20:23:40 +00:00
</g>
< g transform = "rotate(135)" >
2021-06-12 07:03:47 +00:00
< line class = "$sun_color" stroke - linecap = "round" stroke - width = "5" transform = "translate(0,9)" x1 = "0" x2 = "0" y1 = "16" y2 = "24" / >
2021-05-26 20:47:27 +00:00
</g>
2021-05-27 20:23:40 +00:00
< g transform = "rotate(180)" >
2021-06-12 07:03:47 +00:00
< line class = "$sun_color" stroke - linecap = "round" stroke - width = "5" transform = "translate(0,9)" x1 = "0" x2 = "0" y1 = "16" y2 = "24" / >
2021-05-27 20:23:40 +00:00
</g>
< g transform = "rotate(225)" >
2021-06-12 07:03:47 +00:00
< line class = "$sun_color" stroke - linecap = "round" stroke - width = "5" transform = "translate(0,9)" x1 = "0" x2 = "0" y1 = "16" y2 = "24" / >
2021-05-27 20:23:40 +00:00
</g>
< g transform = "rotate(270)" >
2021-06-12 07:03:47 +00:00
< line class = "$sun_color" stroke - linecap = "round" stroke - width = "5" transform = "translate(0,9)" x1 = "0" x2 = "0" y1 = "16" y2 = "24" / >
2021-05-27 20:23:40 +00:00
</g>
< g transform = "rotate(315)" >
2021-06-12 07:03:47 +00:00
< line class = "$sun_color" stroke - linecap = "round" stroke - width = "5" transform = "translate(0,9)" x1 = "0" x2 = "0" y1 = "16" y2 = "24" / >
2021-05-27 20:23:40 +00:00
</g>
2021-06-12 07:03:47 +00:00
< circle cx = "0" cy = "0" class = "$sun_color" r = "16" stroke - width = "2" / >
2021-05-27 20:23:40 +00:00
</g>
2021-05-26 20:47:27 +00:00
2021-06-12 07:03:47 +00:00
< g id = "home" fill = "grey" transform = "translate(150,310),scale(4)" >
2021-05-27 20:23:40 +00:00
< path d = "M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" / >
</g>
2021-05-26 20:47:27 +00:00
2021-06-12 07:03:47 +00:00
< g id = "grid" class = "$grid_color" transform = "translate(0,150),scale(3.5)" >
2021-05-27 20:23:40 +00:00
< path d = "M15.3,2H8.7L2,6.46V10H4V8H8v2.79l-4,9V22H6V20.59l6-3.27,6,3.27V22h2V19.79l-4-9V8h4v2h2V6.46ZM14,4V6H10V4ZM6.3,6,8,4.87V6Zm8,6L15,13.42,12,15,9,13.42,9.65,12ZM7.11,17.71,8.2,15.25l1.71.93Zm8.68-2.46,1.09,2.46-2.8-1.53ZM14,10H10V8h4Zm2-5.13L17.7,6H16Z" / >
</g>
2021-05-28 17:56:19 +00:00
} ;
2021-05-27 20:23:40 +00:00
2021-05-28 17:56:19 +00:00
if ( $ hasbat ) {
$ ret . = qq{
2021-06-12 07:03:47 +00:00
< g class = "$bat_color" transform = "translate(410,135),scale(.33) rotate (90)" >
2021-05-28 17:56:19 +00:00
< path d = "m 134.65625,89.15625 c -6.01649,0 -11,4.983509 -11,11 l 0,180 c 0,6.01649 4.98351,11 11,11 l 95.5,0 c 6.01631,0 11,-4.9825 11,-11 l 0,-180 c 0,-6.016491 -4.98351,-11 -11,-11 l -95.5,0 z m 0,10 95.5,0 c 0.60951,0 1,0.390491 1,1 l 0,180 c 0,0.6085 -0.39231,1 -1,1 l -95.5,0 c -0.60951,0 -1,-0.39049 -1,-1 l 0,-180 c 0,-0.609509 0.39049,-1 1,-1 z" / >
< path d = "m 169.625,69.65625 c -6.01649,0 -11,4.983509 -11,11 l 0,14 10,0 0,-14 c 0,-0.609509 0.39049,-1 1,-1 l 25.5,0 c 0.60951,0 1,0.390491 1,1 l 0,14 10,0 0,-14 c 0,-6.016491 -4.98351,-11 -11,-11 l -25.5,0 z" / >
} ;
2021-06-12 07:03:47 +00:00
2021-05-29 12:56:28 +00:00
$ ret . = '<path d="m 221.141,266.334 c 0,3.313 -2.688,6 -6,6 h -65.5 c -3.313,0 -6,-2.688 -6,-6 v -6 c 0,-3.314 2.687,-6 6,-6 l 65.5,-20 c 3.313,0 6,2.686 6,6 v 26 z"/>' if ( $ soc > 12 ) ;
$ ret . = '<path d="m 221.141,213.667 c 0,3.313 -2.688,6 -6,6 l -65.5,20 c -3.313,0 -6,-2.687 -6,-6 v -20 c 0,-3.313 2.687,-6 6,-6 l 65.5,-20 c 3.313,0 6,2.687 6,6 v 20 z"/>' if ( $ soc > 38 ) ;
$ ret . = '<path d="m 221.141,166.667 c 0,3.313 -2.688,6 -6,6 l -65.5,20 c -3.313,0 -6,-2.687 -6,-6 v -20 c 0,-3.313 2.687,-6 6,-6 l 65.5,-20 c 3.313,0 6,2.687 6,6 v 20 z"/>' if ( $ soc > 63 ) ;
$ ret . = '<path d="m 221.141,120 c 0,3.313 -2.688,6 -6,6 l -65.5,20 c -3.313,0 -6,-2.687 -6,-6 v -26 c 0,-3.313 2.687,-6 6,-6 h 65.5 c 3.313,0 6,2.687 6,6 v 6 z"/>' if ( $ soc > 88 ) ;
2021-05-28 17:56:19 +00:00
$ ret . = '</g>' ;
}
2021-05-27 20:23:40 +00:00
2021-06-12 07:03:47 +00:00
$ ret . = qq{
2021-06-12 08:37:50 +00:00
< g transform = "translate(50,50),scale(0.5)" stroke - width = "27" fill = "none" >
2021-06-12 07:03:47 +00:00
< path id = "pv-home" class = "$csc_style" d = "M300,100 L300,510" / >
< path id = "pv-grid" class = "$cgfi_style" d = "M270,100 L90,270" / >
2021-06-13 09:43:58 +00:00
< path id = "grid-home" class = "$cgc_style" d = "M90,305 L270,510" / >
2021-06-12 07:03:47 +00:00
} ;
2021-05-26 20:47:27 +00:00
2021-06-12 07:03:47 +00:00
$ ret . = qq{
< path id = "bat-home" class = "$batout_style" d = "M502,305 L330,510" / >
< path id = "pv-bat" class = "$batin_style" d = "M330,100 L500,270" / >
} if ( $ hasbat ) ;
2021-05-27 20:23:40 +00:00
2021-05-28 17:56:19 +00:00
2021-06-12 07:03:47 +00:00
$ ret . = qq{ <text class="flowg text" id="pv-txt" x="400" y="15" style="font-size: $fs; text-anchor: start;">$cpv</text> } if ( $ cpv ) ;
$ ret . = qq{ <text class="flowg text" id="bat-txt" x="595" y="370" style="font-size: $fs; text-anchor: middle;">$soc %</text> } if ( $ hasbat ) ;
$ ret . = qq{ <text class="flowg text" id="pv_home-txt" x="330" y="300" style="font-size: $fs; text-anchor: start;">$csc</text> } if ( $ csc ) ;
$ ret . = qq{ <text class="flowg text" id="pv-grid-txt" x="125" y="200" style="font-size: $fs; text-anchor: end;">$cgfi</text> } if ( $ cgfi ) ;
$ ret . = qq{ <text class="flowg text" id="grid-home-txt" x="125" y="420" style="font-size: $fs; text-anchor: end;">$cgc</text> } if ( $ cgc ) ;
$ ret . = qq{ <text class="flowg text" id="batout-txt" x="465" y="420" style="font-size: $fs; text-anchor: start;">$batout</text> } if ( $ batout && $ hasbat ) ;
$ ret . = qq{ <text class="flowg text" id="batin-txt" x="465" y="200" style="font-size: $fs; text-anchor: start;">$batin</text> } if ( $ batin && $ hasbat ) ;
2021-05-27 20:23:40 +00:00
2021-06-10 20:32:37 +00:00
$ ret . = qq{ </g></svg> } ;
2021-05-27 20:23:40 +00:00
2021-05-26 20:47:27 +00:00
return $ ret ;
}
2020-12-13 17:29:15 +00:00
################################################################
# Inject consumer icon
################################################################
sub consinject {
2021-05-30 07:54:58 +00:00
my ( $ hash , $ i , @ consumers ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ ret = "" ;
2020-12-13 17:29:15 +00:00
2021-05-30 07:54:58 +00:00
for ( @ consumers ) {
2020-12-13 17:29:15 +00:00
if ( $ _ ) {
my ( $ cons , $ im , $ start , $ end ) = split ( ':' , $ _ ) ;
2021-05-15 07:36:53 +00:00
Log3 ( $ name , 4 , "$name - Consumer to show -> $cons, relative to current time -> start: $start, end: $end" ) if ( $ i < 1 ) ;
2020-12-13 17:29:15 +00:00
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-03-26 12:03:05 +00:00
$ id = int $ id ;
my $ lang = AttrVal ( "global" , "language" , "EN" ) ;
my $ txt = $ lang eq "DE" ? "txtd" : "txte" ;
2021-01-17 19:13:02 +00:00
2021-01-04 20:25:02 +00:00
if ( defined $ weather_ids { $ id } ) {
2021-03-26 12:03:05 +00:00
return $ weather_ids { $ id } { icon } , encode ( "utf8" , $ weather_ids { $ id } { $ txt } ) ;
2021-01-04 20:25:02 +00:00
}
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
}
2021-01-23 11:46:54 +00:00
################################################################
# benötigte Attribute im DWD Device checken
################################################################
sub checkdwdattr {
2021-04-25 15:33:22 +00:00
my $ name = shift ;
2021-01-23 11:46:54 +00:00
my $ dwddev = shift ;
2021-04-17 07:37:23 +00:00
my $ amref = shift ;
2021-01-23 11:46:54 +00:00
my @ fcprop = map { trim ( $ _ ) } split "," , AttrVal ( $ dwddev , "forecastProperties" , "pattern" ) ;
2021-04-25 15:33:22 +00:00
my $ fcr = AttrVal ( $ dwddev , "forecastResolution" , 3 ) ;
my $ err ;
2021-01-23 11:46:54 +00:00
my @ aneeded ;
2021-04-17 07:37:23 +00:00
for my $ am ( @$ amref ) {
2021-01-23 11:46:54 +00:00
next if ( $ am ~ ~ @ fcprop ) ;
push @ aneeded , $ am ;
}
2021-04-25 15:33:22 +00:00
if ( @ aneeded ) {
$ err = qq{ ERROR - device "$dwddev" -> attribute "forecastProperties" must contain: } . join "," , @ aneeded ;
}
if ( $ fcr != 1 ) {
$ err . = ", " if ( $ err ) ;
$ err . = qq{ ERROR - device "$dwddev" -> attribute "forecastResolution" must be set to "1" } ;
}
2021-05-15 07:36:53 +00:00
Log3 ( $ name , 2 , "$name - $err" ) if ( $ err ) ;
2021-04-25 15:33:22 +00:00
return $ err ;
2021-01-23 11:46:54 +00:00
}
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 {
2021-03-28 08:24:48 +00:00
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ rad = $ paref - > { rad } ; # Nominale Strahlung aus DWD Device
my $ num = $ paref - > { num } ; # Nexthour
2021-04-11 07:54:28 +00:00
my $ uac = $ paref - > { uac } ; # Nutze Autokorrektur (on/off)
2021-03-28 08:24:48 +00:00
my $ t = $ paref - > { t } ; # aktueller Unix Timestamp
my $ fh = $ paref - > { fh } ;
my $ fd = $ paref - > { fd } ;
2020-12-13 17:29:15 +00:00
2021-04-11 07:54:28 +00:00
my $ type = $ hash - > { TYPE } ;
my $ stch = $ data { $ type } { $ name } { strings } ; # String Configuration Hash
my $ pr = 1.0 ; # Performance Ratio (PR)
2021-04-23 18:06:08 +00:00
my $ fh1 = $ fh + 1 ;
2021-04-11 07:54:28 +00:00
my $ chour = strftime "%H" , localtime ( $ t + ( $ num * 3600 ) ) ; # aktuelle Stunde
my $ reld = $ fd == 0 ? "today" : $ fd == 1 ? "tomorrow" : "unknown" ;
2021-01-26 20:38:22 +00:00
2021-06-01 10:46:44 +00:00
my $ pvcorr = ReadingsNum ( $ name , "pvCorrectionFactor_" . sprintf ( "%02d" , $ fh + 1 ) , 1.00 ) ; # PV Korrekturfaktor (auto oder manuell)
2021-04-11 07:54:28 +00:00
my $ hc = $ pvcorr ; # Voreinstellung RAW-Korrekturfaktor
my $ hcfound = "use manual correction factor" ;
2021-04-14 12:18:26 +00:00
my $ hq = "m" ;
2021-03-20 09:05:26 +00:00
2021-03-25 18:12:26 +00:00
my $ clouddamp = AttrVal ( $ name , "cloudFactorDamping" , $ cldampdef ) ; # prozentuale Berücksichtigung des Bewölkungskorrekturfaktors
my $ raindamp = AttrVal ( $ name , "rainFactorDamping" , $ rdampdef ) ; # 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-04-18 16:37:33 +00:00
my $ rainprob = NexthoursVal ( $ hash , "NextHour" . sprintf ( "%02d" , $ num ) , "rainprob" , 0 ) ; # Niederschlagswahrscheinlichkeit> 0,1 mm während der letzten Stunde
my $ rcf = 1 - ( ( ( $ rainprob - $ rain_base ) /100) * $raindamp/ 100 ) ; # Rain Correction Faktor mit Steilheit
2021-04-11 07:54:28 +00:00
my $ cloudcover = NexthoursVal ( $ hash , "NextHour" . sprintf ( "%02d" , $ num ) , "cloudcover" , 0 ) ; # effektive Wolkendecke nächste Stunde X
2021-03-28 08:24:48 +00:00
my $ ccf = 1 - ( ( ( $ cloudcover - $ cloud_base ) /100) * $clouddamp/ 100 ) ; # Cloud Correction Faktor mit Steilheit und Fußpunkt
2021-01-26 20:38:22 +00:00
2021-06-10 20:32:37 +00:00
my $ range = calcRange ( $ cloudcover ) ; # V 0.50.1 # Range errechnen
2021-01-26 20:38:22 +00:00
2021-04-11 07:54:28 +00:00
## Ermitteln des relevanten Autokorrekturfaktors
if ( $ uac eq "on" ) { # Autokorrektur soll genutzt werden
2021-04-14 12:18:26 +00:00
$ hcfound = "yes" ; # Status ob Autokorrekturfaktor im Wertevorrat gefunden wurde
( $ hc , $ hq ) = CircularAutokorrVal ( $ hash , sprintf ( "%02d" , $ fh + 1 ) , $ range , undef ) ; # Korrekturfaktor/KF-Qualität der Stunde des Tages der entsprechenden Bewölkungsrange
$ hq // = 0 ;
2021-04-11 07:54:28 +00:00
if ( ! defined $ hc ) {
2021-06-01 10:46:44 +00:00
$ hcfound = "no" ;
2021-06-01 11:48:42 +00:00
$ hc = 1 ; # keine Korrektur
2021-04-14 12:18:26 +00:00
$ hq = 0 ;
2021-04-11 07:54:28 +00:00
}
2021-04-12 20:08:26 +00:00
}
2021-06-01 11:48:42 +00:00
$ hc = sprintf "%.2f" , $ hc ;
2021-04-12 20:08:26 +00:00
2021-06-02 21:26:06 +00:00
$ data { $ type } { $ name } { nexthours } { "NextHour" . sprintf ( "%02d" , $ num ) } { pvcorrf } = $ hc . "/" . $ hq ;
$ data { $ type } { $ name } { nexthours } { "NextHour" . sprintf ( "%02d" , $ num ) } { cloudrange } = $ range ;
2021-03-19 18:06:34 +00:00
2021-04-23 18:06:08 +00:00
if ( $ fd == 0 && $ fh1 ) {
$ paref - > { pvcorrf } = $ hc . "/" . $ hq ;
$ paref - > { nhour } = sprintf ( "%02d" , $ fh1 ) ;
$ paref - > { histname } = "pvcorrfactor" ;
setPVhistory ( $ paref ) ;
delete $ paref - > { histname } ;
}
2021-04-25 15:33:22 +00:00
my $ pvsum = 0 ;
2021-05-31 17:16:40 +00:00
my $ peaksum = 0 ;
my ( $ lh , $ sq ) ;
2021-01-26 20:38:22 +00:00
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)
2021-05-31 17:16:40 +00:00
$ peak *= 1000 ; # kWp in Wp umrechnen
2021-03-20 21:26:12 +00:00
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-27 17:02:18 +00:00
2021-05-31 17:16:40 +00:00
my $ pv = sprintf "%.1f" , ( $ rad * $ af * $ kJtokWh * $ peak * $ pr * $ ccf * $ rcf ) ;
$ lh = { # Log-Hash zur Ausgabe
"moduleDirection" = > $ moddir ,
"modulePeakString" = > $ peak . " W" ,
"moduleTiltAngle" = > $ ta ,
"Area factor" = > $ af ,
"Cloudcover" = > $ cloudcover ,
"CloudRange" = > $ range ,
"CloudFactorDamping" = > $ clouddamp . " %" ,
"Cloudfactor" = > $ ccf ,
"Rainprob" = > $ rainprob ,
"Rainfactor" = > $ rcf ,
"RainFactorDamping" = > $ raindamp . " %" ,
"Radiation" = > $ rad ,
"Factor kJ to kWh" = > $ kJtokWh ,
"PV generation forecast (raw)" = > $ pv . " Wh"
2021-01-26 20:38:22 +00:00
} ;
2021-05-31 17:16:40 +00:00
$ sq = q{ } ;
2021-01-26 20:38:22 +00:00
for my $ idx ( sort keys % { $ lh } ) {
$ sq . = $ idx . " => " . $ lh - > { $ idx } . "\n" ;
}
2021-05-31 17:16:40 +00:00
Log3 ( $ name , 4 , "$name - PV forecast calc (raw) for $reld Hour " . sprintf ( "%02d" , $ chour + 1 ) . " string $st ->\n$sq" ) ;
2021-01-26 20:38:22 +00:00
2021-04-25 15:33:22 +00:00
$ pvsum += $ pv ;
2021-05-31 17:16:40 +00:00
$ peaksum += $ peak ;
2021-01-26 20:38:22 +00:00
}
2021-05-30 12:31:18 +00:00
$ data { $ type } { $ name } { current } { allstringspeak } = $ peaksum ; # insgesamt installierte Peakleistung in W
2021-05-31 17:16:40 +00:00
$ pvsum *= $ hc ; # Korrekturfaktor anwenden
$ pvsum = $ peaksum if ( $ pvsum > $ peaksum ) ;
2021-04-27 20:48:54 +00:00
my $ logao = qq{ } ;
$ paref - > { pvsum } = $ pvsum ;
$ paref - > { peaksum } = $ peaksum ;
( $ pvsum , $ logao ) = _70percentRule ( $ paref ) ;
2021-05-31 17:16:40 +00:00
$ lh = { # Log-Hash zur Ausgabe
2021-06-01 10:46:44 +00:00
"CloudCorrFoundInStore" = > $ hcfound ,
2021-05-31 17:16:40 +00:00
"PV correction factor" = > $ hc ,
"PV correction quality" = > $ hq ,
"PV generation forecast" = > $ pvsum . " Wh " . $ logao ,
} ;
$ sq = q{ } ;
for my $ idx ( sort keys % { $ lh } ) {
$ sq . = $ idx . " => " . $ lh - > { $ idx } . "\n" ;
}
Log3 ( $ name , 4 , "$name - PV forecast calc for $reld Hour " . sprintf ( "%02d" , $ chour + 1 ) . " summary: \n$sq" ) ;
2021-04-27 20:48:54 +00:00
return $ pvsum ;
}
################################################################
# 70% Regel kalkulieren
################################################################
sub _70percentRule {
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ pvsum = $ paref - > { pvsum } ;
my $ peaksum = $ paref - > { peaksum } ;
my $ num = $ paref - > { num } ; # Nexthour
2021-01-21 22:07:18 +00:00
2021-04-25 15:33:22 +00:00
my $ logao = qq{ } ;
2021-04-27 20:48:54 +00:00
my $ confc = NexthoursVal ( $ hash , "NextHour" . sprintf ( "%02d" , $ num ) , "confc" , 0 ) ;
my $ max70 = $ peaksum / 100 * 70 ;
2020-12-13 17:29:15 +00:00
2021-04-27 20:48:54 +00:00
if ( AttrVal ( $ name , "follow70percentRule" , "0" ) eq "1" && $ pvsum > $ max70 ) {
$ pvsum = $ max70 ;
$ logao = qq{ (reduced by 70 percent rule) } ;
}
if ( AttrVal ( $ name , "follow70percentRule" , "0" ) eq "dynamic" && $ pvsum > $ max70 + $ confc ) {
$ pvsum = $ max70 + $ confc ;
$ logao = qq{ (reduced by 70 percent dynamic rule) } ;
}
2021-04-25 15:33:22 +00:00
2021-04-27 20:48:54 +00:00
$ pvsum = int $ pvsum ;
2021-01-26 20:38:22 +00:00
2021-04-27 20:48:54 +00:00
return ( $ pvsum , $ logao ) ;
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 } ;
2021-04-10 11:27:34 +00:00
my $ t = $ paref - > { t } ; # aktuelle Unix-Zeit
2020-12-27 18:51:53 +00:00
my $ chour = $ paref - > { chour } ;
2021-04-10 11:27:34 +00:00
my $ day = $ paref - > { day } ; # aktueller Tag (01,02,03...31)
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 ) {
2021-05-15 07:36:53 +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 $ idts = ReadingsTimestamp ( $ name , "currentInverterDev" , "" ) ; # Definitionstimestamp des Inverterdevice
2020-12-13 21:48:45 +00:00
return if ( ! $ idts ) ;
2021-04-18 16:37:33 +00:00
$ idts = timestringToTimestamp ( $ idts ) ;
2020-12-13 21:48:45 +00:00
2021-06-10 20:32:37 +00:00
if ( $ t - $ idts < 7200 ) {
my $ rmh = sprintf "%.1f" , ( ( 7200 - ( $ t - $ idts ) ) / 3600 ) ;
2021-05-15 07:36:53 +00:00
Log3 ( $ name , 4 , "$name - Variance calculation in standby. It starts in $rmh hours." ) ;
2020-12-17 15:52:48 +00:00
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 ) {
2021-04-21 10:53:14 +00:00
next if ( ! $ chour || $ h > $ chour ) ;
2021-06-01 14:49:10 +00:00
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-05-15 07:36:53 +00:00
Log3 ( $ name , 5 , "$name - pvCorrectionFactor Hour: " . sprintf ( "%02d" , $ h ) . " already calculated" ) ;
2020-12-27 18:51:53 +00:00
next ;
2021-06-01 11:48:42 +00:00
}
2020-12-16 19:15:48 +00:00
2021-05-15 07:36:53 +00:00
Log3 ( $ name , 5 , "$name - Hour: " . sprintf ( "%02d" , $ h ) . ", Today PVreal: $pvval, PVforecast: $fcval" ) ;
2020-12-15 13:41:10 +00:00
2021-06-03 16:05:30 +00:00
$ paref - > { hour } = $ h ;
my ( $ pvhis , $ fchis , $ dnum , $ range ) = calcAvgFromHistory ( $ paref ) ; # historische PV / Forecast Vergleichswerte ermitteln
2021-06-05 09:36:40 +00:00
$ dnum = $ dnum ? $ dnum + 1 : 1 ; # der aktuelle Wert ist nun der erste AVG im Store
2021-06-03 16:05:30 +00:00
$ pvval = $ pvhis ? ( $ pvval + $ pvhis ) / $ dnum : $ pvval ; # Ertrag aktuelle Stunde berücksichtigen
$ fcval = $ fchis ? ( $ fcval + $ fchis ) / $ dnum : $ fcval ; # Vorhersage aktuelle Stunde berücksichtigen
2021-03-22 20:33:54 +00:00
2021-04-23 18:06:08 +00:00
my ( $ oldfac , $ oldq ) = CircularAutokorrVal ( $ hash , sprintf ( "%02d" , $ h ) , $ range , 0 ) ; # bisher definierter Korrekturfaktor/KF-Qualität der Stunde des Tages der entsprechenden Bewölkungsrange
$ oldfac = 1 if ( 1 * $ oldfac == 0 ) ;
2021-06-05 09:36:40 +00:00
Log3 ( $ name , 4 , "$name - New PV Variance -> range: $range, hour: $h, days: $dnum, real: $pvval, forecast: $fcval" ) ;
2021-03-22 20:33:54 +00:00
2021-04-11 07:54:28 +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-05-15 07:36:53 +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-05-15 07:36:53 +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
}
2021-04-11 15:50:28 +00:00
if ( defined $ range ) {
2021-04-11 07:54:28 +00:00
my $ type = $ hash - > { TYPE } ;
2021-05-15 07:36:53 +00:00
Log3 ( $ name , 5 , "$name - write correction factor into circular Hash: Factor $factor, Hour $h, Range $range" ) ;
2021-04-11 07:54:28 +00:00
2021-04-14 12:18:26 +00:00
$ data { $ type } { $ name } { circular } { sprintf ( "%02d" , $ h ) } { pvcorrf } { $ range } = $ factor ; # Korrekturfaktor für Bewölkung Range 0..10 für die jeweilige Stunde als Datenquelle eintragen
2021-06-03 16:05:30 +00:00
$ data { $ type } { $ name } { circular } { sprintf ( "%02d" , $ h ) } { quality } { $ range } = $ dnum ; # Korrekturfaktor Qualität
2021-04-11 07:54:28 +00:00
}
2021-04-11 15:50:28 +00:00
else {
$ range = "" ;
}
2021-06-03 16:05:30 +00:00
push @ da , "pvCorrectionFactor_" . sprintf ( "%02d" , $ h ) . "<>" . $ factor . " (automatic - old factor: $oldfac, cloudiness range: $range, days in range: $dnum)" ;
2021-04-23 18:06:08 +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
################################################################
2021-04-14 12:18:26 +00:00
# Berechne Durchschnitte PV Vorhersage / PV Ertrag
# aus Werten der PV History
2021-01-19 09:10:20 +00:00
################################################################
2021-04-14 12:18:26 +00:00
sub calcAvgFromHistory {
2021-01-19 09:10:20 +00:00
my $ paref = shift ;
2021-04-10 11:27:34 +00:00
my $ hash = $ paref - > { hash } ;
2021-04-10 08:25:23 +00:00
my $ hour = $ paref - > { hour } ; # Stunde des Tages für die der Durchschnitt bestimmt werden soll
2021-04-10 11:27:34 +00:00
my $ day = $ paref - > { day } ; # aktueller Tag
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 $ 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-04-14 12:18:26 +00:00
for my $ e ( 0 .. $ calcmaxd ) {
last if ( $ e == $ calcmaxd || $ k [ $ ei ] == $ day ) ;
2021-01-20 21:00:34 +00:00
unshift @ efa , $ k [ $ ei ] ;
2021-01-20 08:49:39 +00:00
$ ei - - ;
}
2021-03-14 07:47:14 +00:00
2021-04-14 12:18:26 +00:00
if ( scalar ( @ efa ) ) {
Log3 ( $ name , 4 , "$name - PV History -> Raw Days ($calcmaxd) for average check: " . join " " , @ efa ) ;
2021-03-14 07:47:14 +00:00
}
2021-06-02 21:26:06 +00:00
else { # vermeide Fehler: Illegal division by zero
2021-03-14 07:47:14 +00:00
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
2021-06-02 21:26:06 +00:00
my $ chwcc = HistoryVal ( $ hash , $ day , $ hour , "wcc" , undef ) ; # Wolkenbedeckung Heute & abgefragte Stunde
2021-04-10 08:25:23 +00:00
2021-04-10 08:37:52 +00:00
if ( ! defined $ chwcc ) {
2021-04-10 08:25:23 +00:00
Log3 ( $ name , 4 , "$name - Day $day has no cloudiness value set for hour $hour, no past averages can be calculated." ) ;
return ;
}
2021-06-10 20:32:37 +00:00
my $ range = calcRange ( $ chwcc ) ; # V 0.50.1
2021-04-10 08:25:23 +00:00
2021-04-11 15:50:28 +00:00
Log3 ( $ name , 4 , "$name - cloudiness range of day/hour $day/$hour is: $range" ) ;
2021-04-10 08:25:23 +00:00
2021-06-03 16:05:30 +00:00
my $ dnum = 0 ;
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-04-10 08:37:52 +00:00
my $ histwcc = HistoryVal ( $ hash , $ dayfa , $ hour , "wcc" , undef ) ; # historische Wolkenbedeckung
2021-04-10 08:25:23 +00:00
2021-04-10 08:37:52 +00:00
if ( ! defined $ histwcc ) {
2021-04-10 08:25:23 +00:00
Log3 ( $ name , 4 , "$name - PV History -> Day $dayfa has no cloudiness value set for hour $hour, this history dataset is ignored." ) ;
next ;
}
2021-06-02 21:26:06 +00:00
2021-06-10 20:32:37 +00:00
$ histwcc = calcRange ( $ histwcc ) ; # V 0.50.1
2021-04-10 08:25:23 +00:00
2021-04-11 15:50:28 +00:00
if ( $ range == $ histwcc ) {
2021-04-10 08:25:23 +00:00
$ pvrl += HistoryVal ( $ hash , $ dayfa , $ hour , "pvrl" , 0 ) ;
$ pvfc += HistoryVal ( $ hash , $ dayfa , $ hour , "pvfc" , 0 ) ;
2021-06-03 16:05:30 +00:00
$ dnum + + ;
2021-06-05 09:36:40 +00:00
Log3 ( $ name , 5 , "$name - PV History -> historical Day/hour $dayfa/$hour included - cloudiness range: $range" ) ;
2021-06-03 16:05:30 +00:00
last if ( $ dnum == $ calcd ) ;
2021-04-10 08:25:23 +00:00
}
else {
2021-06-05 09:36:40 +00:00
Log3 ( $ name , 5 , "$name - PV History -> current/historical cloudiness range different: $range/$histwcc Day/hour $dayfa/$hour discarded." ) ;
2021-04-10 08:25:23 +00:00
}
}
2021-06-03 16:05:30 +00:00
if ( ! $ dnum ) {
2021-06-05 09:36:40 +00:00
Log3 ( $ name , 5 , "$name - PV History -> all cloudiness ranges were different/not set -> no historical averages calculated" ) ;
2021-04-11 15:50:28 +00:00
return ( undef , undef , undef , $ range ) ;
2021-01-20 21:00:34 +00:00
}
2021-06-03 16:05:30 +00:00
my $ pvhis = sprintf "%.2f" , $ pvrl ;
my $ fchis = sprintf "%.2f" , $ pvfc ;
2021-01-20 21:00:34 +00:00
2021-06-05 09:36:40 +00:00
Log3 ( $ name , 5 , "$name - PV History -> Summary - cloudiness range: $range, days: $dnum, pvHist:$pvhis, fcHist:$fchis" ) ;
2021-06-03 16:05:30 +00:00
return ( $ pvhis , $ fchis , $ dnum , $ range ) ;
2021-01-20 08:49:39 +00:00
}
2021-01-19 09:10:20 +00:00
return ;
}
2021-06-02 21:26:06 +00:00
################################################################
2021-06-10 20:32:37 +00:00
# Bewölkungs- bzw. Regenrange berechnen
2021-06-02 21:26:06 +00:00
################################################################
2021-06-10 20:32:37 +00:00
sub calcRange {
2021-06-02 21:26:06 +00:00
my $ range = shift ;
$ range = sprintf ( "%.0f" , $ range / 10 ) ;
return $ range ;
}
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 {
2021-05-16 11:24:05 +00:00
my $ paref = shift ;
my $ hash = $ paref - > { hash } ;
my $ name = $ paref - > { name } ;
my $ t = $ paref - > { t } ; # aktuelle Unix-Zeit
my $ nhour = $ paref - > { nhour } ;
my $ day = $ paref - > { day } ;
my $ dayname = $ paref - > { dayname } ; # aktueller Wochentagsname
my $ histname = $ paref - > { histname } // qq{ } ;
my $ ethishour = $ paref - > { ethishour } // 0 ;
my $ etotal = $ paref - > { etotal } ;
my $ batinthishour = $ paref - > { batinthishour } ; # Batterieladung in Stunde
my $ btotin = $ paref - > { batintotal } ; # totale Batterieladung
my $ batoutthishour = $ paref - > { batoutthishour } ; # Batterieentladung in Stunde
my $ btotout = $ paref - > { batouttotal } ; # totale Batterieentladung
my $ calcpv = $ paref - > { calcpv } // 0 ;
my $ gcthishour = $ paref - > { gctotthishour } // 0 ; # Netzbezug
my $ fithishour = $ paref - > { gftotthishour } // 0 ; # Netzeinspeisung
my $ con = $ paref - > { con } // 0 ; # realer Hausverbrauch Energie
my $ confc = $ paref - > { confc } // 0 ; # Verbrauchsvorhersage
my $ consumerco = $ paref - > { consumerco } ; # Verbrauch eines Verbrauchers
my $ wid = $ paref - > { wid } // - 1 ;
my $ wcc = $ paref - > { wcc } // 0 ; # Wolkenbedeckung
my $ wrp = $ paref - > { wrp } // 0 ; # Wahrscheinlichkeit von Niederschlag
2021-06-01 10:46:44 +00:00
my $ pvcorrf = $ paref - > { pvcorrf } // "1.00/0" ; # pvCorrectionFactor
2021-05-16 11:24:05 +00:00
my $ temp = $ paref - > { temp } ; # Außentemperatur
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-04-14 17:12:33 +00:00
$ data { $ type } { $ name } { pvhist } { $ day } { 99 } { dayname } = $ dayname ;
2021-04-04 19:24:55 +00:00
2021-05-16 11:24:05 +00:00
if ( $ histname eq "batinthishour" ) { # Batterieladung
$ val = $ batinthishour ;
$ data { $ type } { $ name } { pvhist } { $ day } { $ nhour } { batin } = $ batinthishour ;
my $ batinsum = 0 ;
for my $ k ( keys % { $ data { $ type } { $ name } { pvhist } { $ day } } ) {
next if ( $ k eq "99" ) ;
$ batinsum += HistoryVal ( $ hash , $ day , $ k , "batin" , 0 ) ;
}
$ data { $ type } { $ name } { pvhist } { $ day } { 99 } { batin } = $ batinsum ;
}
if ( $ histname eq "batoutthishour" ) { # Batterieentladung
$ val = $ batoutthishour ;
$ data { $ type } { $ name } { pvhist } { $ day } { $ nhour } { batout } = $ batoutthishour ;
my $ batoutsum = 0 ;
for my $ k ( keys % { $ data { $ type } { $ name } { pvhist } { $ day } } ) {
next if ( $ k eq "99" ) ;
$ batoutsum += HistoryVal ( $ hash , $ day , $ k , "batout" , 0 ) ;
}
$ data { $ type } { $ name } { pvhist } { $ day } { 99 } { batout } = $ batoutsum ;
}
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" ) ;
2021-03-22 20:33:54 +00:00
$ pvrlsum += HistoryVal ( $ hash , $ day , $ k , "pvrl" , 0 ) ;
2021-03-14 15:46:03 +00:00
}
$ 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" ) ;
2021-03-22 20:33:54 +00:00
$ pvfcsum += HistoryVal ( $ hash , $ day , $ k , "pvfc" , 0 ) ;
2021-03-14 15:46:03 +00:00
}
$ data { $ type } { $ name } { pvhist } { $ day } { 99 } { pvfc } = $ pvfcsum ;
2021-03-14 06:29:17 +00:00
}
2021-05-11 16:42:32 +00:00
if ( $ histname eq "confc" ) { # prognostizierter Hausverbrauch
$ val = $ confc ;
$ data { $ type } { $ name } { pvhist } { $ day } { $ nhour } { confc } = $ confc ;
my $ confcsum = 0 ;
for my $ k ( keys % { $ data { $ type } { $ name } { pvhist } { $ day } } ) {
next if ( $ k eq "99" ) ;
$ confcsum += HistoryVal ( $ hash , $ day , $ k , "confc" , 0 ) ;
}
$ data { $ type } { $ name } { pvhist } { $ day } { 99 } { confc } = $ confcsum ;
}
2021-03-17 20:06:19 +00:00
if ( $ histname eq "cons" ) { # bezogene Energie
2021-04-06 15:50:16 +00:00
$ val = $ gcthishour ;
$ data { $ type } { $ name } { pvhist } { $ day } { $ nhour } { gcons } = $ gcthishour ;
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-22 20:33:54 +00:00
$ gcsum += HistoryVal ( $ hash , $ 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-04-06 15:50:16 +00:00
if ( $ histname eq "gfeedin" ) { # eingespeiste Energie
$ val = $ fithishour ;
$ data { $ type } { $ name } { pvhist } { $ day } { $ nhour } { gfeedin } = $ fithishour ;
my $ gfisum = 0 ;
for my $ k ( keys % { $ data { $ type } { $ name } { pvhist } { $ day } } ) {
next if ( $ k eq "99" ) ;
$ gfisum += HistoryVal ( $ hash , $ day , $ k , "gfeedin" , 0 ) ;
}
$ data { $ type } { $ name } { pvhist } { $ day } { 99 } { gfeedin } = $ gfisum ;
}
2021-04-14 12:18:26 +00:00
if ( $ histname eq "con" ) { # Energieverbrauch des Hauses
$ val = $ con ;
$ data { $ type } { $ name } { pvhist } { $ day } { $ nhour } { con } = $ con ;
my $ consum = 0 ;
for my $ k ( keys % { $ data { $ type } { $ name } { pvhist } { $ day } } ) {
next if ( $ k eq "99" ) ;
$ consum += HistoryVal ( $ hash , $ day , $ k , "con" , 0 ) ;
}
$ data { $ type } { $ name } { pvhist } { $ day } { 99 } { con } = $ consum ;
}
2021-05-14 06:59:34 +00:00
if ( $ histname =~ /csm[et][0-9]+$/xs ) { # Verbrauch eines Verbrauchers
2021-05-02 20:24:39 +00:00
$ val = $ consumerco ;
$ data { $ type } { $ name } { pvhist } { $ day } { $ nhour } { $ histname } = $ consumerco ;
if ( $ histname =~ /csme[0-9]+$/xs ) {
my $ sum = 0 ;
my $ hours = 0 ;
for my $ k ( keys % { $ data { $ type } { $ name } { pvhist } { $ day } } ) {
next if ( $ k eq "99" ) ;
my $ csme = HistoryVal ( $ hash , $ day , $ k , "$histname" , 0 ) ;
next if ( ! $ csme ) ;
$ sum += $ csme ;
$ hours + + ;
}
$ data { $ type } { $ name } { pvhist } { $ day } { 99 } { $ histname } = $ sum ;
$ data { $ type } { $ name } { pvhist } { $ day } { 99 } { "hours" . $ histname } = $ hours ;
}
}
2021-05-14 06:59:34 +00:00
if ( $ histname eq "etotal" ) { # etotal des Wechselrichters
$ val = $ etotal ;
$ data { $ type } { $ name } { pvhist } { $ day } { $ nhour } { etotal } = $ etotal ;
$ data { $ type } { $ name } { pvhist } { $ day } { 99 } { etotal } = q{ } ;
}
2021-05-16 11:24:05 +00:00
if ( $ histname eq "batintotal" ) { # totale Batterieladung
$ val = $ btotin ;
$ data { $ type } { $ name } { pvhist } { $ day } { $ nhour } { batintotal } = $ btotin ;
$ data { $ type } { $ name } { pvhist } { $ day } { 99 } { batintotal } = q{ } ;
}
if ( $ histname eq "batouttotal" ) { # totale Batterieentladung
$ val = $ btotout ;
$ data { $ type } { $ name } { pvhist } { $ day } { $ nhour } { batouttotal } = $ btotout ;
$ data { $ type } { $ name } { pvhist } { $ day } { 99 } { batouttotal } = q{ } ;
}
2021-04-06 15:50:16 +00:00
if ( $ histname eq "weatherid" ) { # Wetter ID
2021-03-23 22:01:47 +00:00
$ val = $ wid ;
2021-04-18 16:37:33 +00:00
$ data { $ type } { $ name } { pvhist } { $ day } { $ nhour } { weatherid } = $ wid ;
$ data { $ type } { $ name } { pvhist } { $ day } { 99 } { weatherid } = q{ } ;
2021-03-23 22:01:47 +00:00
}
2021-04-07 16:05:08 +00:00
if ( $ histname eq "weathercloudcover" ) { # Wolkenbedeckung
$ val = $ wcc ;
2021-04-18 16:37:33 +00:00
$ data { $ type } { $ name } { pvhist } { $ day } { $ nhour } { wcc } = $ wcc ;
2021-04-07 16:05:08 +00:00
$ data { $ type } { $ name } { pvhist } { $ day } { 99 } { wcc } = q{ } ;
}
if ( $ histname eq "weatherrainprob" ) { # Niederschlagswahrscheinlichkeit
$ val = $ wrp ;
2021-04-18 16:37:33 +00:00
$ data { $ type } { $ name } { pvhist } { $ day } { $ nhour } { wrp } = $ wrp ;
2021-04-07 16:05:08 +00:00
$ data { $ type } { $ name } { pvhist } { $ day } { 99 } { wrp } = q{ } ;
}
2021-04-10 11:27:34 +00:00
if ( $ histname eq "pvcorrfactor" ) { # pvCorrectionFactor
$ val = $ pvcorrf ;
2021-04-18 16:37:33 +00:00
$ data { $ type } { $ name } { pvhist } { $ day } { $ nhour } { pvcorrf } = $ pvcorrf ;
$ data { $ type } { $ name } { pvhist } { $ day } { 99 } { pvcorrf } = q{ } ;
}
if ( $ histname eq "temperature" ) { # Außentemperatur
$ val = $ temp ;
$ data { $ type } { $ name } { pvhist } { $ day } { $ nhour } { temp } = $ temp ;
$ data { $ type } { $ name } { pvhist } { $ day } { 99 } { temp } = q{ } ;
2021-04-10 11:27:34 +00:00
}
2021-04-23 18:06:08 +00:00
Log3 ( $ name , 5 , "$name - set PV History day: $day, 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-05-16 11:24:05 +00:00
my $ pvrl = HistoryVal ( $ hash , $ day , $ key , "pvrl" , "-" ) ;
my $ pvfc = HistoryVal ( $ hash , $ day , $ key , "pvfc" , "-" ) ;
my $ gcon = HistoryVal ( $ hash , $ day , $ key , "gcons" , "-" ) ;
my $ con = HistoryVal ( $ hash , $ day , $ key , "con" , "-" ) ;
my $ confc = HistoryVal ( $ hash , $ day , $ key , "confc" , "-" ) ;
my $ gfeedin = HistoryVal ( $ hash , $ day , $ key , "gfeedin" , "-" ) ;
my $ wid = HistoryVal ( $ hash , $ day , $ key , "weatherid" , "-" ) ;
my $ wcc = HistoryVal ( $ hash , $ day , $ key , "wcc" , "-" ) ;
my $ wrp = HistoryVal ( $ hash , $ day , $ key , "wrp" , "-" ) ;
my $ temp = HistoryVal ( $ hash , $ day , $ key , "temp" , undef ) ;
my $ pvcorrf = HistoryVal ( $ hash , $ day , $ key , "pvcorrf" , "-" ) ;
my $ dayname = HistoryVal ( $ hash , $ day , $ key , "dayname" , undef ) ;
my $ etotal = HistoryVal ( $ hash , $ day , $ key , "etotal" , "-" ) ;
my $ btotin = HistoryVal ( $ hash , $ day , $ key , "batintotal" , "-" ) ;
my $ batin = HistoryVal ( $ hash , $ day , $ key , "batin" , "-" ) ;
my $ btotout = HistoryVal ( $ hash , $ day , $ key , "batouttotal" , "-" ) ;
my $ batout = HistoryVal ( $ hash , $ day , $ key , "batout" , "-" ) ;
2021-05-02 20:24:39 +00:00
$ ret . = "\n " if ( $ ret ) ;
2021-05-14 06:59:34 +00:00
$ ret . = $ key . " => etotal: $etotal, pvfc: $pvfc, pvrl: $pvrl" ;
$ ret . = "\n " ;
$ ret . = "confc: $confc, con: $con, gcon: $gcon, gfeedin: $gfeedin" ;
2021-05-02 20:24:39 +00:00
$ ret . = "\n " ;
2021-05-16 11:24:05 +00:00
$ ret . = "batintotal: $btotin, batin: $batin, batouttotal: $btotout, batout: $batout" ;
$ ret . = "\n " ;
2021-05-02 20:24:39 +00:00
$ ret . = "wid: $wid" ;
$ ret . = ", wcc: $wcc" ;
$ ret . = ", wrp: $wrp" ;
$ ret . = ", temp: $temp" if ( $ temp ) ;
$ ret . = ", pvcorrf: $pvcorrf" ;
$ ret . = ", dayname: $dayname" if ( $ dayname ) ;
my $ csm ;
for my $ c ( 1 .. $ maxconsumer ) {
$ c = sprintf "%02d" , $ c ;
my $ csmt = HistoryVal ( $ hash , $ day , $ key , "csmt${c}" , undef ) ;
my $ csme = HistoryVal ( $ hash , $ day , $ key , "csme${c}" , undef ) ;
my $ csmh = HistoryVal ( $ hash , $ day , $ key , "hourscsme${c}" , undef ) ;
if ( defined $ csmt ) {
$ csm . = ", " if ( $ csm ) ;
$ csm . = "csmt${c}: $csmt" ;
}
if ( defined $ csme ) {
$ csm . = ", " if ( $ csm ) ;
$ csm . = "csme${c}: $csme" ;
}
if ( defined $ csmh ) {
$ csm . = ", " if ( $ csm ) ;
$ csm . = "hourscsme${c}: $csmh" ;
}
}
if ( $ csm ) {
$ ret . = "\n " ;
$ ret . = $ csm ;
}
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. } ;
}
2021-05-02 20:24:39 +00:00
for my $ idx ( sort { $ a <=> $ b } keys % { $ h } ) {
2021-03-17 20:06:19 +00:00
$ sq . = $ idx . " => " . $ sub - > ( $ idx ) . "\n" ;
}
2021-01-17 19:13:02 +00:00
}
2021-05-02 20:24:39 +00:00
if ( $ htol eq "consumer" ) {
$ h = $ data { $ type } { $ name } { consumers } ;
if ( ! keys % { $ h } ) {
return qq{ Consumer cache is empty. } ;
}
for my $ idx ( sort { $ a <=> $ b } keys % { $ h } ) {
my $ cret ;
for my $ ckey ( sort keys % { $ h - > { $ idx } } ) {
if ( ref $ h - > { $ idx } { $ ckey } eq "HASH" ) {
my $ hk ;
for my $ f ( sort { $ a <=> $ b } keys % { $ h - > { $ idx } { $ ckey } } ) {
$ hk . = " " if ( $ hk ) ;
$ hk . = "$f=" . $ h - > { $ idx } { $ ckey } { $ f } ;
}
$ cret . = $ ckey . " => " . $ hk . "\n " ;
}
else {
$ cret . = $ ckey . " => " . $ h - > { $ idx } { $ ckey } . "\n " ;
}
}
$ sq . = $ idx . " => " . $ cret . "\n" ;
}
}
2021-03-23 16:49:43 +00:00
if ( $ htol eq "circular" ) {
$ h = $ data { $ type } { $ name } { circular } ;
2021-03-18 17:25:37 +00:00
if ( ! keys % { $ h } ) {
2021-03-23 16:49:43 +00:00
return qq{ Circular cache is empty. } ;
2021-03-18 17:25:37 +00:00
}
2021-03-23 16:49:43 +00:00
for my $ idx ( sort keys % { $ h } ) {
2021-04-11 07:54:28 +00:00
my $ pvfc = CircularVal ( $ hash , $ idx , "pvfc" , "-" ) ;
my $ pvrl = CircularVal ( $ hash , $ idx , "pvrl" , "-" ) ;
2021-05-12 11:12:59 +00:00
my $ confc = CircularVal ( $ hash , $ idx , "confc" , "-" ) ;
2021-04-11 07:54:28 +00:00
my $ gcons = CircularVal ( $ hash , $ idx , "gcons" , "-" ) ;
my $ gfeedin = CircularVal ( $ hash , $ idx , "gfeedin" , "-" ) ;
my $ wid = CircularVal ( $ hash , $ idx , "weatherid" , "-" ) ;
my $ wtxt = CircularVal ( $ hash , $ idx , "weathertxt" , "-" ) ;
my $ wccv = CircularVal ( $ hash , $ idx , "wcc" , "-" ) ;
my $ wrprb = CircularVal ( $ hash , $ idx , "wrp" , "-" ) ;
2021-04-18 16:37:33 +00:00
my $ temp = CircularVal ( $ hash , $ idx , "temp" , "-" ) ;
2021-04-11 07:54:28 +00:00
my $ pvcorrf = CircularVal ( $ hash , $ idx , "pvcorrf" , "-" ) ;
2021-04-14 12:18:26 +00:00
my $ quality = CircularVal ( $ hash , $ idx , "quality" , "-" ) ;
2021-05-16 11:24:05 +00:00
my $ batin = CircularVal ( $ hash , $ idx , "batin" , "-" ) ;
my $ batout = CircularVal ( $ hash , $ idx , "batout" , "-" ) ;
2021-04-11 07:54:28 +00:00
my $ pvcf ;
if ( ref $ pvcorrf eq "HASH" ) {
2021-04-11 10:08:19 +00:00
for my $ f ( sort { $ a <=> $ b } keys % { $ h - > { $ idx } { pvcorrf } } ) {
2021-04-11 07:54:28 +00:00
$ pvcf . = " " if ( $ pvcf ) ;
$ pvcf . = "$f=" . $ h - > { $ idx } { pvcorrf } { $ f } ;
}
}
else {
$ pvcf = $ pvcorrf ;
}
2021-04-14 12:18:26 +00:00
my $ cfq ;
if ( ref $ quality eq "HASH" ) {
for my $ q ( sort { $ a <=> $ b } keys % { $ h - > { $ idx } { quality } } ) {
$ cfq . = " " if ( $ cfq ) ;
$ cfq . = "$q=" . $ h - > { $ idx } { quality } { $ q } ;
}
}
else {
$ cfq = $ quality ;
}
2021-04-11 07:54:28 +00:00
$ sq . = "\n" if ( $ sq ) ;
2021-05-16 11:24:05 +00:00
$ sq . = $ idx . " => pvfc: $pvfc, pvrl: $pvrl, batin: $batin, batout: $batout\n" ;
$ sq . = " confc: $confc, gcon: $gcons, gfeedin: $gfeedin, wcc: $wccv, wrp: $wrprb\n" ;
$ sq . = " temp: $temp, wid: $wid, wtxt: $wtxt\n" ;
2021-04-14 12:18:26 +00:00
$ sq . = " corr: $pvcf\n" ;
2021-04-27 20:48:54 +00:00
$ sq . = " quality: $cfq" ;
2021-03-18 17:25:37 +00:00
}
}
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-04-12 20:08:26 +00:00
my $ nhts = NexthoursVal ( $ hash , $ idx , "starttime" , "-" ) ;
2021-05-09 18:29:53 +00:00
my $ today = NexthoursVal ( $ hash , $ idx , "today" , "-" ) ;
my $ pvfc = NexthoursVal ( $ hash , $ idx , "pvforecast" , "-" ) ;
2021-04-12 20:08:26 +00:00
my $ wid = NexthoursVal ( $ hash , $ idx , "weatherid" , "-" ) ;
my $ neff = NexthoursVal ( $ hash , $ idx , "cloudcover" , "-" ) ;
2021-06-02 21:26:06 +00:00
my $ crange = NexthoursVal ( $ hash , $ idx , "cloudrange" , "-" ) ;
2021-04-12 20:08:26 +00:00
my $ r101 = NexthoursVal ( $ hash , $ idx , "rainprob" , "-" ) ;
my $ rad1h = NexthoursVal ( $ hash , $ idx , "Rad1h" , "-" ) ;
my $ pvcorrf = NexthoursVal ( $ hash , $ idx , "pvcorrf" , "-" ) ;
2021-04-18 16:37:33 +00:00
my $ temp = NexthoursVal ( $ hash , $ idx , "temp" , "-" ) ;
my $ confc = NexthoursVal ( $ hash , $ idx , "confc" , "-" ) ;
2021-04-12 20:08:26 +00:00
$ sq . = "\n" if ( $ sq ) ;
2021-06-02 21:26:06 +00:00
$ sq . = $ idx . " => starttime: $nhts, today: $today\n" ;
$ sq . = " pvfc: $pvfc, confc: $confc, Rad1h: $rad1h\n" ;
$ sq . = " wid: $wid, wcc: $neff, wrp: $r101, temp=$temp\n" ;
$ sq . = " crange: $crange, correff: $pvcorrf" ;
2021-03-20 09:05:26 +00:00
}
}
2021-03-28 08:24:48 +00:00
2021-04-17 07:37:23 +00:00
if ( $ htol eq "qualities" ) {
$ h = $ data { $ type } { $ name } { nexthours } ;
if ( ! keys % { $ h } ) {
return qq{ NextHours cache is empty. } ;
}
for my $ idx ( sort keys % { $ h } ) {
2021-04-18 16:37:33 +00:00
my $ nhfc = NexthoursVal ( $ hash , $ idx , "pvforecast" , undef ) ;
next if ( ! $ nhfc ) ;
2021-04-17 07:37:23 +00:00
my $ nhts = NexthoursVal ( $ hash , $ idx , "starttime" , undef ) ;
2021-06-02 21:26:06 +00:00
my $ neff = NexthoursVal ( $ hash , $ idx , "cloudcover" , "-" ) ;
my $ crange = NexthoursVal ( $ hash , $ idx , "cloudrange" , "-" ) ;
my $ pvcorrf = NexthoursVal ( $ hash , $ idx , "pvcorrf" , "-/-" ) ;
2021-04-17 07:37:23 +00:00
my ( $ f , $ q ) = split "/" , $ pvcorrf ;
$ sq . = "\n" if ( $ sq ) ;
2021-06-02 21:26:06 +00:00
$ sq . = "starttime: $nhts, wcc: $neff, crange: $crange, quality: $q, used factor: $f" ;
2021-04-17 07:37:23 +00:00
}
}
2021-03-28 08:24:48 +00:00
if ( $ htol eq "current" ) {
$ h = $ data { $ type } { $ name } { current } ;
if ( ! keys % { $ h } ) {
2021-04-18 16:37:33 +00:00
return qq{ Current values cache is empty. } ;
2021-03-28 08:24:48 +00:00
}
for my $ idx ( sort keys % { $ h } ) {
2021-04-04 19:24:55 +00:00
if ( ref $ h - > { $ idx } ne "ARRAY" ) {
$ sq . = $ idx . " => " . $ h - > { $ idx } . "\n" ;
}
else {
my $ aser = join " " , @ { $ h - > { $ idx } } ;
$ sq . = $ idx . " => " . $ aser . "\n" ;
}
2021-03-28 08:24:48 +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 } ;
2021-06-03 16:05:30 +00:00
my $ lang = AttrVal ( "global" , 'language' , 'EN' ) ;
2021-01-26 20:38:22 +00:00
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 } ) {
2021-06-03 16:05:30 +00:00
my $ sp = $ sn . " => " . $ sub - > ( $ sn ) . "<br>" ;
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-06-03 16:05:30 +00:00
$ sc . = "<br><br>" . encode ( "utf8" , $ hqtxt { strnok } { $ lang } ) ;
2021-01-26 20:38:22 +00:00
}
else {
2021-06-03 16:05:30 +00:00
$ sc . = "<br><br>" . encode ( "utf8" , $ hqtxt { strok } { $ lang } ) ;
2021-01-26 20:38:22 +00:00
}
return $ sc ;
}
2021-04-05 14:54:45 +00:00
################################################################
# Array auf eine festgelegte Anzahl Elemente beschränken,
# Das älteste Element wird entfernt
#
# $href = Referenz zum Array
# $limit = die Anzahl Elemente auf die gekürzt werden soll
# (default 3)
#
################################################################
sub limitArray {
my $ href = shift ;
my $ limit = shift // 3 ;
return if ( ref $ href ne "ARRAY" ) ;
while ( scalar @ { $ href } > $ limit ) {
shift @ { $ href } ;
}
return ;
}
2021-05-11 16:42:32 +00:00
################################################################
# Timestrings berechnen
################################################################
sub timestampToTimestring {
my $ epoch = shift ;
my ( $ lyear , $ lmonth , $ lday , $ lhour , $ lmin , $ lsec ) = ( localtime ( $ epoch ) ) [ 5 , 4 , 3 , 2 , 1 , 0 ] ;
my $ ts ;
$ lyear += 1900 ; # year is 1900 based
$ lmonth + + ; # month number is zero based
my ( $ sec , $ min , $ hour , $ day , $ mon , $ year ) = ( localtime ( time ) ) [ 0 , 1 , 2 , 3 , 4 , 5 ] ; # Standard f. z.B. Readingstimstamp
$ year += 1900 ;
$ mon + + ;
my $ realts = sprintf ( "%04d-%02d-%02d %02d:%02d:%02d" , $ year , $ mon , $ day , $ hour , $ min , $ sec ) ;
my $ tsdef = sprintf ( "%04d-%02d-%02d %02d:%s" , $ lyear , $ lmonth , $ lday , $ lhour , "00:00" ) ; # engl. Variante für Logging-Timestamps etc. (Minute/Sekunde == 00)
my $ tsfull = sprintf ( "%04d-%02d-%02d %02d:%02d:%02d" , $ lyear , $ lmonth , $ lday , $ lhour , $ lmin , $ lsec ) ; # engl. Variante Vollzeit
if ( AttrVal ( "global" , "language" , "EN" ) eq "DE" ) {
$ ts = sprintf ( "%02d.%02d.%04d %02d:%s" , $ lday , $ lmonth , $ lyear , $ lhour , "00:00" ) ;
}
else {
$ ts = $ tsdef ;
}
return ( $ ts , $ tsdef , $ realts , $ tsfull ) ;
}
2020-12-13 17:29:15 +00:00
################################################################
2020-12-27 18:51:53 +00:00
# einen Zeitstring YYYY-MM-TT hh:mm:ss in einen Unix
# Timestamp umwandeln
################################################################
sub timestringToTimestamp {
my $ tstring = shift ;
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 ) {
2021-04-04 07:45:35 +00:00
my ( $ rn , $ rval , $ ts ) = split "<>" , $ elem , 3 ;
2021-04-02 19:58:48 +00:00
readingsBulkUpdate ( $ hash , $ rn , $ rval , undef , $ ts ) ;
2020-12-13 17:29:15 +00:00
}
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 . '$' ;
2021-03-28 08:24:48 +00:00
for my $ reading ( grep { /$readingspec/x } keys % { $ hash - > { READINGS } } ) {
2020-12-16 09:15:37 +00:00
readingsDelete ( $ hash , $ reading ) ;
}
return ;
}
2020-12-13 17:29:15 +00:00
######################################################################################
# NOTIFYDEV erstellen
######################################################################################
sub createNotifyDev {
my $ hash = shift ;
my $ name = $ hash - > { NAME } ;
2021-05-16 19:57:43 +00:00
my $ type = $ hash - > { TYPE } ;
2020-12-13 17:29:15 +00:00
RemoveInternalTimer ( $ hash , "FHEM::SolarForecast::createNotifyDev" ) ;
if ( $ init_done == 1 ) {
my @ nd ;
2021-05-16 19:57:43 +00:00
my ( $ afc , $ ara , $ ain , $ ame , $ aba , $ h ) ;
2020-12-15 13:41:10 +00:00
2021-04-24 08:05:49 +00:00
my $ fcdev = ReadingsVal ( $ name , "currentForecastDev" , "" ) ; # Weather forecast Device
2021-03-28 08:24:48 +00:00
( $ afc , $ h ) = parseParams ( $ fcdev ) ;
$ fcdev = $ afc - > [ 0 ] // "" ;
2020-12-13 17:29:15 +00:00
2021-04-17 07:37:23 +00:00
my $ radev = ReadingsVal ( $ name , "currentRadiationDev" , "" ) ; # Radiation forecast Device
( $ ara , $ h ) = parseParams ( $ radev ) ;
$ radev = $ ara - > [ 0 ] // "" ;
2021-04-24 08:05:49 +00:00
my $ indev = ReadingsVal ( $ name , "currentInverterDev" , "" ) ; # Inverter Device
2021-03-28 08:24:48 +00:00
( $ ain , $ h ) = parseParams ( $ indev ) ;
$ indev = $ ain - > [ 0 ] // "" ;
2020-12-15 13:41:10 +00:00
2021-04-24 08:05:49 +00:00
my $ medev = ReadingsVal ( $ name , "currentMeterDev" , "" ) ; # Meter Device
2021-03-28 08:24:48 +00:00
( $ ame , $ h ) = parseParams ( $ medev ) ;
$ medev = $ ame - > [ 0 ] // "" ;
2020-12-13 17:29:15 +00:00
2021-05-02 20:24:39 +00:00
my $ badev = ReadingsVal ( $ name , "currentBatteryDev" , "" ) ; # Battery Device
2021-04-24 08:05:49 +00:00
( $ aba , $ h ) = parseParams ( $ badev ) ;
$ badev = $ aba - > [ 0 ] // "" ;
2021-05-16 19:57:43 +00:00
for my $ c ( sort { $ a <=> $ b } keys % { $ data { $ type } { $ name } { consumers } } ) { # Consumer Devices
my $ codev = AttrVal ( $ name , "consumer${c}" , "" ) ;
my ( $ ac , $ hc ) = parseParams ( $ codev ) ;
$ codev = $ ac - > [ 0 ] // "" ;
push @ nd , $ codev if ( $ codev ) ;
}
2021-05-02 20:24:39 +00:00
2020-12-13 17:29:15 +00:00
push @ nd , $ fcdev ;
2021-04-17 07:37:23 +00:00
push @ nd , $ radev if ( $ radev ne $ fcdev ) ;
2020-12-13 17:29:15 +00:00
push @ nd , $ indev ;
push @ nd , $ medev ;
2021-05-15 08:37:12 +00:00
push @ nd , $ badev ;
2020-12-13 17:29:15 +00:00
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 ;
}
2021-05-09 18:29:53 +00:00
################################################################
# Planungsdaten Consumer löschen
# $c - Consumer Nummer
################################################################
sub deleteConsumerPlanning {
my $ hash = shift ;
my $ c = shift ;
my $ type = $ hash - > { TYPE } ;
my $ name = $ hash - > { NAME } ;
delete $ data { $ type } { $ name } { consumers } { $ c } { planstate } ;
delete $ data { $ type } { $ name } { consumers } { $ c } { planswitchon } ;
2021-05-11 16:42:32 +00:00
delete $ data { $ type } { $ name } { consumers } { $ c } { planswitchoff } ;
deleteReadingspec ( $ hash , "consumer${c}.*" ) ;
2021-05-09 18:29:53 +00:00
return ;
}
2021-05-16 11:24:05 +00:00
###############################################################################
2021-03-22 20:33:54 +00:00
# Wert des pvhist-Hash zurückliefern
# Usage:
# HistoryVal ($hash, $day, $hod, $key, $def)
#
# $day: Tag des Monats (01,02,...,31)
# $hod: Stunde des Tages (01,02,...,24,99)
2021-05-16 11:24:05 +00:00
# $key: etotal - totale PV Erzeugung (Wh)
# pvrl - realer PV Ertrag
# pvfc - PV Vorhersage
# confc - Vorhersage Hausverbrauch (Wh)
# gcons - realer Netzbezug
# gfeedin - reale Netzeinspeisung
# batintotal - totale Batterieladung (Wh)
# batin - Batterieladung der Stunde (Wh)
# batouttotal - totale Batterieentladung (Wh)
# batout - Batterieentladung der Stunde (Wh)
# weatherid - Wetter ID
# wcc - Grad der Bewölkung
# temp - Außentemperatur
# wrp - Niederschlagswahrscheinlichkeit
# pvcorrf - PV Autokorrekturfaktor f. Stunde des Tages
# dayname - Tagesname (Kürzel)
# csmt${c} - Totalconsumption Consumer $c (1..$maxconsumer)
# csme${c} - Consumption Consumer $c (1..$maxconsumer) in $hod
2021-03-23 22:01:47 +00:00
# $def: Defaultwert
2021-03-22 20:33:54 +00:00
#
2021-05-16 11:24:05 +00:00
###############################################################################
2021-03-22 20:33:54 +00:00
sub HistoryVal {
my $ hash = shift ;
my $ day = shift ;
my $ hod = shift ;
my $ key = shift ;
my $ def = shift ;
my $ name = $ hash - > { NAME } ;
my $ type = $ hash - > { TYPE } ;
if ( defined ( $ data { $ type } { $ name } { pvhist } ) &&
defined ( $ data { $ type } { $ name } { pvhist } { $ day } ) &&
defined ( $ data { $ type } { $ name } { pvhist } { $ day } { $ hod } ) &&
defined ( $ data { $ type } { $ name } { pvhist } { $ day } { $ hod } { $ key } ) ) {
return $ data { $ type } { $ name } { pvhist } { $ day } { $ hod } { $ key } ;
}
return $ def ;
}
2021-03-23 16:49:43 +00:00
################################################################
# Wert des circular-Hash zurückliefern
2021-04-11 07:54:28 +00:00
# Achtung: die Werte im circular-Hash haben nicht
# zwingend eine Beziehung zueinander !!
#
2021-03-23 16:49:43 +00:00
# Usage:
# CircularVal ($hash, $hod, $key, $def)
#
2021-03-23 22:01:47 +00:00
# $hod: Stunde des Tages (01,02,...,24)
2021-04-11 07:54:28 +00:00
# $key: pvrl - realer PV Ertrag
# pvfc - PV Vorhersage
2021-05-12 11:12:59 +00:00
# confc - Vorhersage Hausverbrauch (Wh)
2021-04-11 07:54:28 +00:00
# gcons - realer Netzbezug
# gfeedin - reale Netzeinspeisung
2021-05-16 11:24:05 +00:00
# batin - Batterieladung (Wh)
# batout - Batterieentladung (Wh)
2021-04-11 07:54:28 +00:00
# weatherid - DWD Wetter id
# weathertxt - DWD Wetter Text
# wcc - DWD Wolkendichte
# wrp - DWD Regenwahrscheinlichkeit
2021-04-18 16:37:33 +00:00
# temp - Außentemperatur
2021-04-11 07:54:28 +00:00
# pvcorrf - PV Autokorrekturfaktoren (HASH)
2021-03-23 22:01:47 +00:00
# $def: Defaultwert
2021-03-23 16:49:43 +00:00
#
################################################################
sub CircularVal {
my $ hash = shift ;
my $ hod = shift ;
my $ key = shift ;
my $ def = shift ;
my $ name = $ hash - > { NAME } ;
my $ type = $ hash - > { TYPE } ;
if ( defined ( $ data { $ type } { $ name } { circular } ) &&
defined ( $ data { $ type } { $ name } { circular } { $ hod } ) &&
defined ( $ data { $ type } { $ name } { circular } { $ hod } { $ key } ) ) {
return $ data { $ type } { $ name } { circular } { $ hod } { $ key } ;
}
return $ def ;
}
2021-04-11 07:54:28 +00:00
################################################################
2021-04-14 12:18:26 +00:00
# Wert des Autokorrekturfaktors und dessen Qualität
# für eine bestimmte Bewölkungs-Range aus dem circular-Hash
# zurückliefern
2021-04-11 07:54:28 +00:00
# Usage:
2021-04-14 12:18:26 +00:00
# ($f,$q) = CircularAutokorrVal ($hash, $hod, $range, $def)
#
# $f: Korrekturfaktor f. Stunde des Tages
2021-05-09 18:29:53 +00:00
# $q: Qualität des Korrekturfaktors
2021-04-11 07:54:28 +00:00
#
# $hod: Stunde des Tages (01,02,...,24)
# $range: Range Bewölkung (1...10)
# $def: Defaultwert
#
################################################################
sub CircularAutokorrVal {
my $ hash = shift ;
my $ hod = shift ;
my $ range = shift ;
my $ def = shift ;
my $ name = $ hash - > { NAME } ;
my $ type = $ hash - > { TYPE } ;
2021-04-14 12:18:26 +00:00
my $ pvcorrf = $ def ;
my $ quality = $ def ;
2021-04-11 07:54:28 +00:00
if ( defined ( $ data { $ type } { $ name } { circular } ) &&
defined ( $ data { $ type } { $ name } { circular } { $ hod } ) &&
defined ( $ data { $ type } { $ name } { circular } { $ hod } { pvcorrf } ) &&
defined ( $ data { $ type } { $ name } { circular } { $ hod } { pvcorrf } { $ range } ) ) {
2021-04-14 12:18:26 +00:00
$ pvcorrf = $ data { $ type } { $ name } { circular } { $ hod } { pvcorrf } { $ range } ;
}
if ( defined ( $ data { $ type } { $ name } { circular } ) &&
defined ( $ data { $ type } { $ name } { circular } { $ hod } ) &&
defined ( $ data { $ type } { $ name } { circular } { $ hod } { quality } ) &&
defined ( $ data { $ type } { $ name } { circular } { $ hod } { quality } { $ range } ) ) {
$ quality = $ data { $ type } { $ name } { circular } { $ hod } { quality } { $ range } ;
2021-04-11 07:54:28 +00:00
}
2021-04-14 12:18:26 +00:00
return ( $ pvcorrf , $ quality ) ;
2021-04-11 07:54:28 +00:00
}
2021-03-23 22:01:47 +00:00
################################################################
# Wert des nexthours-Hash zurückliefern
# Usage:
# NexthoursVal ($hash, $hod, $key, $def)
#
# $hod: nächste Stunde (NextHour00, NextHour01,...)
# $key: starttime - Startzeit der abgefragten nächsten Stunde
# pvforecast - PV Vorhersage
# weatherid - DWD Wetter id
# cloudcover - DWD Wolkendichte
2021-06-02 21:26:06 +00:00
# cloudrange - berechnete Bewölkungsrange
2021-03-23 22:01:47 +00:00
# rainprob - DWD Regenwahrscheinlichkeit
# Rad1h - Globalstrahlung (kJ/m2)
2021-04-21 10:53:14 +00:00
# confc - Vorhersage Hausverbrauch (Wh)
2021-05-09 18:29:53 +00:00
# today - 1 wenn heute
2021-03-23 22:01:47 +00:00
# $def: Defaultwert
#
################################################################
sub NexthoursVal {
my $ hash = shift ;
my $ hod = shift ;
my $ key = shift ;
my $ def = shift ;
my $ name = $ hash - > { NAME } ;
my $ type = $ hash - > { TYPE } ;
if ( defined ( $ data { $ type } { $ name } { nexthours } ) &&
defined ( $ data { $ type } { $ name } { nexthours } { $ hod } ) &&
defined ( $ data { $ type } { $ name } { nexthours } { $ hod } { $ key } ) ) {
return $ data { $ type } { $ name } { nexthours } { $ hod } { $ key } ;
}
return $ def ;
}
2021-05-30 07:54:58 +00:00
#############################################################################
2021-04-05 08:29:56 +00:00
# Wert des current-Hash zurückliefern
# Usage:
# CurrentVal ($hash, $key, $def)
2021-04-04 06:40:40 +00:00
#
2021-04-18 16:37:33 +00:00
# $key: generation - aktuelle PV Erzeugung
# genslidereg - Schieberegister PV Erzeugung (Array)
# h4fcslidereg - Schieberegister 4h PV Forecast (Array)
2021-05-30 07:54:58 +00:00
# consumerdevs - alle registrierten Consumerdevices (Array)
2021-04-18 16:37:33 +00:00
# gridconsumption - aktueller Netzbezug
# powerbatin - Batterie Ladeleistung
# powerbatout - Batterie Entladeleistung
# temp - aktuelle Außentemperatur
# tomorrowconsumption - Verbrauch des kommenden Tages
2021-04-05 14:54:45 +00:00
# $def: Defaultwert
2021-04-04 06:40:40 +00:00
#
2021-05-30 07:54:58 +00:00
#############################################################################
2021-04-04 06:40:40 +00:00
sub CurrentVal {
my $ hash = shift ;
my $ key = shift ;
my $ def = shift ;
my $ name = $ hash - > { NAME } ;
my $ type = $ hash - > { TYPE } ;
2021-04-04 19:24:55 +00:00
if ( defined ( $ data { $ type } { $ name } { current } ) &&
2021-04-04 06:40:40 +00:00
defined ( $ data { $ type } { $ name } { current } { $ key } ) ) {
return $ data { $ type } { $ name } { current } { $ key } ;
}
return $ def ;
}
2021-05-02 20:24:39 +00:00
#######################################################################
# Wert des consumer-Hash zurückliefern
# Usage:
# ConsumerVal ($hash, $co, $key, $def)
#
# $co: Consumer Nummer (01,02,03,...)
# $key: name - Name des Verbrauchers (Device)
# alias - Alias des Verbrauchers (Device)
# type - Typ des Verbrauchers
# power - nominale Leistungsaufnahme des Verbrauchers in W
# mode - Planungsmode des Verbrauchers
# icon - Icon für den Verbraucher
# mintime - min. Einschalt- bzw. Zykluszeit
# oncom - Setter Einschaltkommando
# offcom - Setter Ausschaltkommando
# retotal - Reading der Leistungsmessung
# uetotal - Unit der Leistungsmessung
# avgenergy - gemessener Durchschnittsverbrauch eines Tages
# epieces - prognostizierte Energiescheiben (Hash)
2021-06-12 12:32:43 +00:00
# isConsumptionRecommended - ist Verbrauch empfohlen ?
2021-05-02 20:24:39 +00:00
#
# $def: Defaultwert
#
######################################################################
sub ConsumerVal {
my $ hash = shift ;
my $ co = shift ;
my $ key = shift ;
my $ def = shift ;
my $ name = $ hash - > { NAME } ;
my $ type = $ hash - > { TYPE } ;
if ( defined ( $ data { $ type } { $ name } { consumers } ) &&
defined ( $ data { $ type } { $ name } { consumers } { $ co } { $ key } ) &&
defined ( $ data { $ type } { $ name } { consumers } { $ co } { $ key } ) ) {
return $ data { $ type } { $ name } { consumers } { $ co } { $ key } ;
}
return $ def ;
}
2020-12-13 17:29:15 +00:00
1 ;
= pod
2021-05-30 18:37:08 +00:00
= item summary Visualization of solar predictions for PV systems and Consumer control
= item summary_DE Visualisierung von solaren Vorhersagen für PV Anlagen und Verbrauchersteuerung
2020-12-13 17:29:15 +00:00
= begin html
2021-05-30 18:37:08 +00:00
< a id = "SolarForecast" > </a>
<h3> SolarForecast </h3>
<br>
2020-12-13 17:29:15 +00:00
= end html
= begin html_DE
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast" > </a>
2020-12-13 17:29:15 +00:00
<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
2021-04-17 10:34:21 +00:00
Vorhersage für den solaren Ertrag und integriert weitere Informationen als Grundlage für darauf aufbauende 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>
2021-05-17 09:22:49 +00:00
Abhängig von den DWD - Daten und der physikalischen Anlagengestaltung ( Ausrichtung , Winkel , Aufteilung in mehrere Strings , u . a . )
wird auf Grundlage der prognostizierten Globalstrahlung eine wahrscheinliche PV Erzeugung der kommenden Stunden ermittelt . <br>
Darüber hinaus werden Verbrauchswerte bzw . Netzbezugswerte erfasst und für eine Verbrauchsprognose verwendet . <br>
Das Modul errechnet aus den Prognosewerten einen zukünftigen Energieüberschuß der zur Betriebsplanung von Verbrauchern
genutzt wird . Der Nutzer kann Verbraucher ( z . B . Schaltsteckdosen ) direkt im Modul registrieren und die Planung der
Ein / Ausschaltzeiten sowie deren Ausführung vom SolarForecast Modul übernehmen lassen .
2020-12-13 17:29:15 +00:00
<ul>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-define" > </a>
2020-12-13 17:29:15 +00:00
<b> Define </b>
<br> <br>
<ul>
2021-04-12 20:08:26 +00:00
Ein SolarForecast Device wird erstellt mit: <br> <br>
2020-12-13 17:29:15 +00:00
<ul>
<b> define & lt ; name & gt ; SolarForecast </b>
</ul>
<br>
2021-04-17 10:34:21 +00:00
Nach der Definition des Devices sind zwingend Vorhersage - Devices des Typs DWD_OpenData zuzuordnen sowie weitere
anlagenspezifische Angaben mit den entsprechenden set - Kommandos zu hinterlegen . <br>
2021-01-19 09:10:20 +00:00
Mit nachfolgenden set - Kommandos werden die Quellendevices und Quellenreadings für maßgebliche Informationen
hinterlegt: <br> <br>
<ul>
2021-05-30 18:37:08 +00:00
<table>
2021-01-19 09:10:20 +00:00
<colgroup> < col width = 35 % > < col width = 65 % > < / colgroup >
2021-05-17 09:22:49 +00:00
<tr> <td> <b> currentForecastDev </b> </td> <td> DWD_OpenData Device welches Wetterdaten liefert </td> </tr>
<tr> <td> <b> currentRadiationDev </b> </td> <td> DWD_OpenData Device welches Strahlungsdaten liefert </td> </tr>
2021-03-26 12:03:05 +00:00
<tr> <td> <b> currentInverterDev </b> </td> <td> Device welches PV Leistungsdaten liefert </td> </tr>
2021-04-09 21:09:20 +00:00
<tr> <td> <b> currentMeterDev </b> </td> <td> Device welches Netz I /O-Daten liefert </ td > </tr>
<tr> <td> <b> currentBatteryDev </b> </td> <td> Device welches Batterie Leistungsdaten liefert </td> </tr>
2021-01-19 09:10:20 +00:00
</table>
</ul>
<br>
Um eine Anpassung an die persönliche Anlage zu ermöglichen , können Korrekturfaktoren manuell
2021-04-12 20:08:26 +00:00
( set & lt ; name & gt ; pvCorrectionFactor_XX ) bzw . automatisiert ( set & lt ; name & gt ; pvCorrectionFactor_Auto on ) bestimmt
werden . <br>
Es wird empfohlen die automatische Vorhersagekorrektur unmittelbar einzuschalten , da das SolarForecast Device etliche Tage
benötigt um eine Optimierung der Korrekturfaktoren zu erreichen .
2020-12-13 17:29:15 +00:00
<br> <br>
</ul>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-set" > </a>
2020-12-13 17:29:15 +00:00
<b> Set </b>
<ul>
2021-04-09 21:09:20 +00:00
<ul>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-set-currentBatteryDev" > </a>
2021-06-03 16:05:30 +00:00
<li> <b> currentBatteryDev & lt ; Meter Device Name & gt ; pin = & lt ; Readingname & gt ; : & lt ; Einheit & gt ; pout = & lt ; Readingname & gt ; : & lt ; Einheit & gt ; [ intotal = & lt ; Readingname & gt ; : & lt ; Einheit & gt ; ] [ outtotal = & lt ; Readingname & gt ; : & lt ; Einheit & gt ; ] [ charge = & lt ; Readingname & gt ; ] </b> <br> <br>
2021-04-09 21:09:20 +00:00
Legt ein beliebiges Device und seine Readings zur Lieferung der Batterie Leistungsdaten fest .
2021-04-10 08:25:23 +00:00
Das Modul geht davon aus dass der numerische Wert der Readings immer positiv ist .
2021-04-09 21:09:20 +00:00
Es kann auch ein Dummy Device mit entsprechenden Readings sein . Die Bedeutung des jeweiligen "Readingname" ist:
<br>
<ul>
<table>
<colgroup> < col width = 15 % > < col width = 85 % > < / colgroup >
2021-05-16 11:24:05 +00:00
<tr> <td> <b> pin </b> </td> <td> Reading welches die aktuelle Batterieladung liefert </td> </tr>
<tr> <td> <b> pout </b> </td> <td> Reading welches die aktuelle Batterieentladung liefert </td> </tr>
<tr> <td> <b> intotal </b> </td> <td> Reading welches die totale Batterieladung liefert ( fortlaufender Zähler ) </td> </tr>
<tr> <td> <b> outtotal </b> </td> <td> Reading welches die totale Batterieentladung liefert ( fortlaufender Zähler ) </td> </tr>
2021-05-27 20:23:40 +00:00
<tr> <td> <b> charge </b> </td> <td> Reading welches den aktuellen Ladezustand ( in Prozent ) liefert </td> </tr>
2021-05-16 11:24:05 +00:00
<tr> <td> <b> Einheit </b> </td> <td> die jeweilige Einheit ( W , Wh , kW , kWh ) </td> </tr>
2021-04-09 21:09:20 +00:00
</table>
</ul>
<br>
<b> Sonderfälle: </b> Sollte das Reading für pin und pout identisch , aber vorzeichenbehaftet sein ,
können die Schlüssel pin und pout wie folgt definiert werden: <br> <br>
<ul>
pin = - pout & nbsp ; & nbsp ; & nbsp ; ( ein negativer Wert von pout wird als pin verwendet ) <br>
pout = - pin & nbsp ; & nbsp ; & nbsp ; ( ein negativer Wert von pin wird als pout verwendet )
</ul>
<br>
Die Einheit entfällt in dem jeweiligen Sonderfall . <br> <br>
<ul>
<b> Beispiel: </b> <br>
2021-05-16 11:24:05 +00:00
set & lt ; name & gt ; currentBatteryDev BatDummy pin = BatVal:W pout = - pin intotal = BatInTot:Wh outtotal = BatOutTot:Wh <br>
2021-04-09 21:09:20 +00:00
<br>
2021-05-16 11:24:05 +00:00
# Device BatDummy liefert die aktuelle Batterieladung im Reading "BatVal" (W), die Batterieentladung im gleichen Reading mit negativen Vorzeichen, <br>
# die summarische Ladung im Reading "intotal" (Wh), sowie die summarische Entladung im Reading "outtotal" (Wh)
2021-04-09 21:09:20 +00:00
</ul>
</li>
</ul>
<br>
2020-12-13 17:29:15 +00:00
<ul>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-set-currentForecastDev" > </a>
2021-04-05 06:20:53 +00:00
<li> <b> currentForecastDev </b> <br> <br>
2021-04-17 07:37:23 +00:00
Legt das Device ( Typ DWD_OpenData ) fest , welches die Wetterdaten ( Bewölkung , Niederschlag , usw . ) 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>
2020-12-13 17:29:15 +00:00
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 >
2021-04-17 07:37:23 +00:00
<tr> <td> <b> forecastDays </b> </td> <td> 1 </td> </tr>
<tr> <td> <b> forecastProperties </b> </td> <td> TTT , Neff , R101 , ww , SunUp , SunRise , SunSet </td> </tr>
<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>
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>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-set-currentInverterDev" > </a>
2021-04-06 15:50:16 +00:00
<li> <b> currentInverterDev & lt ; Inverter Device Name & gt ; pv = & lt ; Readingname & gt ; : & lt ; Einheit & gt ; etotal = & lt ; Readingname & gt ; : & lt ; Einheit & gt ; </b> <br> <br>
2021-04-05 06:20:53 +00:00
2021-04-06 15:50:16 +00:00
Legt ein beliebiges Device und dessen Readings zur Lieferung der aktuellen PV Erzeugungswerte fest .
Es kann auch ein Dummy Device mit entsprechenden Readings sein .
Die Werte mehrerer Inverterdevices führt man z . B . in einem Dummy Device zusammen und gibt dieses Device mit den
entsprechenden Readings an . Die Bedeutung des jeweiligen "Readingname" ist:
2021-01-23 12:15:30 +00:00
<br>
2021-03-14 18:20:19 +00:00
<ul>
<table>
2021-04-04 06:40:40 +00:00
<colgroup> < col width = 15 % > < col width = 85 % > < / colgroup >
2021-04-06 15:50:16 +00:00
<tr> <td> <b> pv </b> </td> <td> Reading welches die aktuelle PV - Erzeugung liefert </td> </tr>
<tr> <td> <b> etotal </b> </td> <td> Reading welches die gesamte erzeugten Energie liefert ( ein stetig aufsteigender Zähler ) </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-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-04-07 09:27:33 +00:00
<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>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-set-currentMeterDev" > </a>
2021-04-06 15:50:16 +00:00
<li> <b> currentMeterDev & lt ; Meter Device Name & gt ; gcon = & lt ; Readingname & gt ; : & lt ; Einheit & gt ; contotal = & lt ; Readingname & gt ; : & lt ; Einheit & gt ; gfeedin = & lt ; Readingname & gt ; : & lt ; Einheit & gt ; feedtotal = & lt ; Readingname & gt ; : & lt ; Einheit & gt ; </b> <br> <br>
2021-04-05 06:20:53 +00:00
2021-04-10 08:25:23 +00:00
Legt ein beliebiges Device und seine Readings zur Energiemessung fest .
Das Modul geht davon aus dass der numerische Wert der Readings immer positiv ist .
2021-04-06 15:50:16 +00:00
Es kann auch ein Dummy Device mit entsprechenden Readings sein . Die Bedeutung des jeweiligen "Readingname" ist:
2021-03-14 18:20:19 +00:00
<br>
<ul>
<table>
<colgroup> < col width = 15 % > < col width = 85 % > < / colgroup >
2021-04-06 15:50:16 +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> gfeedin </b> </td> <td> Reading welches die aktuell in das Netz eingespeiste Leistung liefert </td> </tr>
<tr> <td> <b> feedtotal </b> </td> <td> Reading welches die Summe der in das Netz eingespeisten 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
2021-04-09 16:11:17 +00:00
<b> Sonderfälle: </b> Sollte das Reading für gcon und gfeedin identisch , aber vorzeichenbehaftet sein ,
können die Schlüssel gfeedin und gcon wie folgt definiert werden: <br> <br>
2021-04-07 09:27:33 +00:00
<ul>
2021-04-09 16:11:17 +00:00
gfeedin = - gcon & nbsp ; & nbsp ; & nbsp ; ( ein negativer Wert von gcon wird als gfeedin verwendet ) <br>
gcon = - gfeedin & nbsp ; & nbsp ; & nbsp ; ( ein negativer Wert von gfeedin wird als gcon verwendet )
2021-04-07 09:27:33 +00:00
</ul>
<br>
2021-04-09 16:11:17 +00:00
Die Einheit entfällt in dem jeweiligen Sonderfall . <br> <br>
2021-04-07 09:27:33 +00:00
2020-12-15 13:41:10 +00:00
<ul>
<b> Beispiel: </b> <br>
2021-04-07 09:27:33 +00:00
set & lt ; name & gt ; currentMeterDev Meter gcon = Wirkleistung:W contotal = BezWirkZaehler:kWh gfeedin = - gcon feedtotal = EinWirkZaehler:kWh <br>
<br>
# Device Meter liefert den aktuellen Netzbezug im Reading "Wirkleistung" (W),
die Summe des Netzbezugs im Reading "BezWirkZaehler" ( kWh ) , die aktuelle Einspeisung in "Wirkleistung" wenn "Wirkleistung" negativ ist ,
die Summe der Einspeisung im Reading "EinWirkZaehler" ( kWh )
2020-12-15 13:41:10 +00:00
</ul>
2020-12-13 17:29:15 +00:00
</li>
</ul>
<br>
2021-04-17 07:37:23 +00:00
<ul>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-set-currentRadiationDev" > </a>
2021-04-17 07:37:23 +00:00
<li> <b> currentRadiationDev </b> <br> <br>
Legt das Device ( Typ DWD_OpenData ) fest , welches die solaren Strahlungsdaten 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>
2021-05-30 18:37:08 +00:00
<table>
2021-04-17 07:37:23 +00:00
<colgroup> < col width = 25 % > < col width = 75 % > < / colgroup >
<tr> <td> <b> forecastDays </b> </td> <td> 1 </td> </tr>
<tr> <td> <b> forecastProperties </b> </td> <td> Rad1h </td> </tr>
<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>
</table>
</ul>
</li>
</ul>
<br>
2021-04-05 14:54:45 +00:00
<ul>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-set-energyH4Trigger" > </a>
2021-04-05 14:54:45 +00:00
<li> <b> energyH4Trigger & lt ; 1 on & gt ; = & lt ; Wert & gt ; & lt ; 1 off & gt ; = & lt ; Wert & gt ; [ & lt ; 2 on & gt ; = & lt ; Wert & gt ; & lt ; 2 off & gt ; = & lt ; Wert & gt ; ... ] </b> <br> <br>
Generiert Trigger bei Über - bzw . Unterschreitung der 4 - Stunden PV Vorhersage ( NextHours_Sum04_PVforecast ) . <br>
Überschreiten die letzten drei Messungen der 4 - Stunden PV Vorhersagen eine definierte <b> Xon - Bedingung </b> , wird das Reading
2021-04-05 16:45:41 +00:00
<b> energyH4Trigger_X = on </b> erstellt / gesetzt .
2021-04-05 14:54:45 +00:00
Unterschreiten die letzten drei Messungen der 4 - Stunden PV Vorhersagen eine definierte <b> Xoff - Bedingung </b> , wird das Reading
2021-04-05 16:45:41 +00:00
<b> energyH4Trigger_X = off </b> erstellt / gesetzt . <br>
2021-04-05 14:54:45 +00:00
Es kann eine beliebige Anzahl von Triggerbedingungen angegeben werden . Xon / Xoff - Bedingungen müssen nicht zwingend paarweise
definiert werden . <br>
<br>
<ul>
<b> Beispiel: </b> <br>
set & lt ; name & gt ; energyH4Trigger 1 on = 2000 1 off = 1700 2 on = 2500 2 off = 2000 3 off = 1500 <br>
</ul>
</li>
</ul>
<br>
2021-01-26 20:38:22 +00:00
<ul>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-set-inverterStrings" > </a>
2021-04-05 06:20:53 +00:00
<li> <b> inverterStrings & lt ; Stringname1 & gt ; [ , & lt ; Stringname2 & gt ; , & lt ; Stringname3 & gt ; , ... ] </b> <br> <br>
2021-01-26 20:38:22 +00:00
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-05-02 20:24:39 +00:00
< a id = "SolarForecast-set-modulePeakString" > </a>
2021-04-05 06:20:53 +00:00
<li> <b> modulePeakString & lt ; Stringname1 & gt ; = & lt ; Peak & gt ; [ & lt ; Stringname2 & gt ; = & lt ; Peak & gt ; & lt ; Stringname3 & gt ; = & lt ; Peak & gt ; ... ] </b> <br> <br>
2021-01-27 17:02:18 +00:00
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>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-set-moduleDirection" > </a>
2021-04-05 06:20:53 +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> <br>
2021-01-26 20:38:22 +00:00
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>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-set-moduleTiltAngle" > </a>
2021-04-05 06:20:53 +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> <br>
2021-01-26 20:38:22 +00:00
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>
2021-06-03 16:05:30 +00:00
<ul>
< a id = "SolarForecast-set-plantConfiguration" > </a>
<li> <b> plantConfiguration </b> <br> <br>
Je nach ausgewählter Kommandooption werden folgende Operationen ausgeführt: <br> <br>
<ul>
<table>
<colgroup> < col width = 25 % > < col width = 75 % > < / colgroup >
<tr> <td> <b> check </b> </td> <td> Zeigt die aktuelle Stringkonfiguration . Es wird gleichzeitig eine Plausibilitätsprüfung </td> </tr>
<tr> <td> </td> <td> vorgenommen und das Ergebnis sowie eventuelle Anweisungen zur Fehlerbehebung ausgegeben . </td> </tr>
<tr> <td> <b> save </b> </td> <td> sichert wichtige Parameter der Anlagenkonfiguration </td> </tr>
<tr> <td> <b> restore </b> </td> <td> stellt eine gesicherte Anlagenkonfiguration wieder her </td> </tr>
</table>
</ul>
</li>
</ul>
<br>
2021-04-04 07:45:35 +00:00
<ul>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-set-powerTrigger" > </a>
2021-04-05 06:20:53 +00:00
<li> <b> powerTrigger & lt ; 1 on & gt ; = & lt ; Wert & gt ; & lt ; 1 off & gt ; = & lt ; Wert & gt ; [ & lt ; 2 on & gt ; = & lt ; Wert & gt ; & lt ; 2 off & gt ; = & lt ; Wert & gt ; ... ] </b> <br> <br>
Generiert Trigger bei Über - bzw . Unterschreitung bestimmter PV Erzeugungswerte ( Current_PV ) . <br>
2021-04-04 19:24:55 +00:00
Überschreiten die letzten drei Messungen der PV Erzeugung eine definierte <b> Xon - Bedingung </b> , wird das Reading
<b> powerTrigger_X = on </b> erstellt / gesetzt .
Unterschreiten die letzten drei Messungen der PV Erzeugung eine definierte <b> Xoff - Bedingung </b> , wird das Reading
2021-04-04 07:45:35 +00:00
<b> powerTrigger_X = off </b> erstellt / gesetzt . <br>
2021-04-04 19:24:55 +00:00
Es kann eine beliebige Anzahl von Triggerbedingungen angegeben werden . Xon / Xoff - Bedingungen müssen nicht zwingend paarweise
definiert werden . <br>
2021-04-04 07:45:35 +00:00
<br>
<ul>
<b> Beispiel: </b> <br>
set & lt ; name & gt ; powerTrigger 1 on = 1000 1 off = 500 2 on = 2000 2 off = 1000 3 on = 1600 4 off = 1100 <br>
</ul>
</li>
</ul>
<br>
2020-12-15 13:41:10 +00:00
<ul>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-set-pvCorrectionFactor_Auto" > </a>
2021-04-05 06:20:53 +00:00
<li> <b> pvCorrectionFactor_Auto & lt ; on | off & gt ; </b> <br> <br>
2021-04-11 10:08:19 +00:00
Schaltet die automatische Vorhersagekorrektur ein / aus . <br>
2021-06-10 20:32:37 +00:00
Ist die Automatik eingeschaltet , wird für jede Stunde ein Korrekturfaktor der Solarvorhersage berechnet und intern
gespeichert .
2021-04-12 20:08:26 +00:00
Dazu wird die tatsächliche Energieerzeugung mit dem vorhergesagten Wert des aktuellen Tages und Stunde verglichen ,
2021-04-11 10:08:19 +00:00
die Korrekturwerte historischer Tage unter Berücksichtigung der Bewölkung einbezogen und daraus ein neuer Korrekturfaktor
abgeleitet . Es werden nur historische Daten mit gleicher Bewölkungsrange einbezogen . <br>
2021-04-12 20:08:26 +00:00
Zukünftig erwartete PV Erzeugungen werden mit den gespeicherten Korrekturfaktoren optimiert . <br>
2021-04-17 10:34:21 +00:00
Bei aktivierter Autokorrektur wird das Attribut < a href = "#cloudFactorDamping" > cloudFactorDamping </a> übersteuert und hat
nur noch eine untergeordnete Bedeutung . <br>
2021-04-12 20:08:26 +00:00
<b> Die automatische Vorhersagekorrektur ist lernend und benötigt einige Tage um die Korrekturwerte zu optimieren .
2021-04-11 10:20:13 +00:00
Nach der Aktivierung sind nicht sofort optimale Vorhersagen zu erwarten ! </b> <br>
2020-12-15 13:41:10 +00:00
( default: off )
</li>
</ul>
<br>
2020-12-13 17:29:15 +00:00
<ul>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-set-pvCorrectionFactor_" data - pattern = "pvCorrectionFactor_.*" > </a>
2021-04-05 06:20:53 +00:00
<li> <b> pvCorrectionFactor_XX & lt ; Zahl & gt ; </b> <br> <br>
2020-12-13 17:29:15 +00:00
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>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-set-reset" > </a>
2021-04-05 06:20:53 +00:00
<li> <b> reset </b> <br> <br>
2021-05-09 18:29:53 +00:00
Löscht die aus der Drop - Down Liste gewählte Datenquelle , zu der Funktion gehörende Readings oder weitere interne
Datenstrukturen . Die Bedeutung der Argumente ist: <br> <br>
<ul>
<table>
2021-05-21 09:35:17 +00:00
<colgroup> < col width = 25 % > < col width = 75 % > < / colgroup > </td> </tr>
<tr> <td> <b> consumerPlanning </b> </td> <td> löscht die Planungsdaten aller registrierten Verbraucher </td> </tr>
<tr> <td> </td> <td> Um die Planungsdaten nur eines Verbrauchers zu löschen verwendet man: </td> </tr>
<tr> <td> </td> <td> set & lt ; name & gt ; reset consumerPlanning & lt ; Verbrauchernummer & gt ; </td> </tr>
<tr> <td> <b> pvHistory </b> </td> <td> löscht den Speicher aller historischen Tage ( 01 ... 31 ) </td> </tr>
<tr> <td> </td> <td> Um einen bestimmten historischen Tag zu löschen: </td> </tr>
<tr> <td> </td> <td> set & lt ; name & gt ; reset pvHistory & lt ; Tag & gt ; ( z . B . set & lt ; name & gt ; reset pvHistory 08 ) </td> </tr>
<tr> <td> </td> <td> Um eine bestimmte Stunde eines historischer Tages zu löschen: </td> </tr>
<tr> <td> </td> <td> set & lt ; name & gt ; reset pvHistory & lt ; Tag & gt ; & lt ; Stunde & gt ; ( z . B . set & lt ; name & gt ; reset pvHistory 08 10 ) </td> </tr>
2021-05-09 18:29:53 +00:00
</table>
</ul>
2021-01-19 17:23:24 +00:00
</li>
</ul>
<br>
2021-01-23 21:31:11 +00:00
<ul>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-set-writeHistory" > </a>
2021-04-05 06:20:53 +00:00
<li> <b> writeHistory </b> <br> <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>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-get" > </a>
2020-12-13 17:29:15 +00:00
<b> Get </b>
2021-05-02 20:24:39 +00:00
<ul>
2020-12-13 17:29:15 +00:00
<ul>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-get-data" > </a>
2021-06-01 21:05:53 +00:00
<li> <b> data </b> <br> <br>
2021-04-17 07:37:23 +00:00
Startet die Datensammlung zur Bestimmung der solaren Vorhersage und anderer Werte .
2020-12-13 17:29:15 +00:00
</li>
</ul>
<br>
2020-12-15 13:54:31 +00:00
<ul>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-get-forecastQualities" > </a>
2021-06-01 21:05:53 +00:00
<li> <b> forecastQualities </b> <br> <br>
2021-04-17 07:37:23 +00:00
Zeigt die aktuell verwendeten Korrekturfaktoren mit der jeweiligen Startzeit zur Bestimmung der PV Vorhersage sowie
deren Qualitäten an .
Die Qualität ergibt sich aus der Anzahl der bereits in der Vergangenheit bewerteten Tage mit einer
identischen Bewölkungsrange .
</li>
</ul>
<br>
<ul>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-get-html" > </a>
2021-06-01 21:05:53 +00:00
<li> <b> html </b> <br> <br>
2021-04-17 07:37:23 +00:00
Die Solar Grafik wird als HTML - Code abgerufen und wiedergegeben .
2020-12-15 13:54:31 +00:00
</li>
</ul>
<br>
2021-03-20 11:36:59 +00:00
<ul>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-get-nextHours" > </a>
2021-06-01 21:05:53 +00:00
<li> <b> nextHours </b> <br> <br>
2021-04-12 20:08:26 +00:00
Listet die erwarteten Werte der kommenden Stunden auf . <br> <br>
<ul>
<table>
2021-04-13 07:18:40 +00:00
<colgroup> < col width = 8 % > < col width = 92 % > < / colgroup >
2021-06-01 11:10:44 +00:00
<tr> <td> <b> pvfc </b> </td> <td> erwartete PV Erzeugung </td> </tr>
<tr> <td> <b> today </b> </td> <td> = 1 wenn Startdatum am aktuellen Tag </td> </tr>
<tr> <td> <b> confc </b> </td> <td> erwarteter Energieverbrauch </td> </tr>
<tr> <td> <b> wid </b> </td> <td> ID des vorhergesagten Wetters </td> </tr>
<tr> <td> <b> wcc </b> </td> <td> vorhergesagter Grad der Bewölkung </td> </tr>
2021-06-02 21:26:06 +00:00
<tr> <td> <b> crange </b> </td> <td> berechneter Bewölkungsbereich </td> </tr>
2021-06-01 11:10:44 +00:00
<tr> <td> <b> correff </b> </td> <td> effektiv verwendeter Korrekturfaktor /Qualität </ td > </tr>
<tr> <td> </td> <td> Faktor /m - manuell </ td > </tr>
<tr> <td> </td> <td> Faktor /0 - Korrekturfaktor nicht in Store vorhanden (default wird verwendet) </ td > </tr>
<tr> <td> </td> <td> Faktor /1...X - Korrekturfaktor aus Store genutzt (höhere Zahl = bessere Qualität) </ td > </tr>
<tr> <td> <b> wrp </b> </td> <td> vorhergesagter Grad der Regenwahrscheinlichkeit </td> </tr>
<tr> <td> <b> Rad1h </b> </td> <td> vorhergesagte Globalstrahlung </td> </tr>
2021-06-02 21:26:06 +00:00
<tr> <td> <b> temp </b> </td> <td> vorhergesagte Außentemperatur </td> </tr>
2021-04-12 20:08:26 +00:00
</table>
</ul>
2021-03-20 11:36:59 +00:00
</li>
</ul>
<br>
2021-01-17 19:13:02 +00:00
<ul>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-get-pvHistory" > </a>
2021-06-01 21:05:53 +00:00
<li> <b> pvHistory </b> <br> <br>
2021-03-19 13:17:55 +00:00
Listet die historischen Werte der letzten Tage ( max . 31 ) sortiert nach dem Tagesdatum und Stunde .
2021-03-23 16:49:43 +00:00
Die Stundenangaben beziehen sich auf die jeweilige Stunde des Tages , z . B . bezieht sich die Stunde 09 auf die Zeit
von 08 - 09 Uhr . <br> <br>
<ul>
<table>
2021-05-02 20:24:39 +00:00
<colgroup> < col width = 20 % > < col width = 80 % > < / colgroup >
2021-05-16 14:36:17 +00:00
<tr> <td> <b> etotal </b> </td> <td> totaler Energieertrag ( Wh ) zu Beginn der Stunde </td> </tr>
2021-05-11 16:42:32 +00:00
<tr> <td> <b> pvfc </b> </td> <td> der prognostizierte PV Ertrag ( Wh ) </td> </tr>
<tr> <td> <b> pvrl </b> </td> <td> reale PV Erzeugung ( Wh ) </td> </tr>
<tr> <td> <b> gcon </b> </td> <td> realer Leistungsbezug ( Wh ) aus dem Stromnetz </td> </tr>
2021-05-12 11:12:59 +00:00
<tr> <td> <b> confc </b> </td> <td> erwarteter Energieverbrauch ( Wh ) </td> </tr>
2021-05-11 16:42:32 +00:00
<tr> <td> <b> con </b> </td> <td> realer Energieverbrauch ( Wh ) des Hauses </td> </tr>
<tr> <td> <b> gfeedin </b> </td> <td> reale Einspeisung ( Wh ) in das Stromnetz </td> </tr>
2021-05-16 14:36:17 +00:00
<tr> <td> <b> batintotal </b> </td> <td> totale Batterieladung ( Wh ) zu Beginn der Stunde </td> </tr>
2021-05-16 11:24:05 +00:00
<tr> <td> <b> batin </b> </td> <td> Batterieladung der Stunde ( Wh ) </td> </tr>
2021-05-16 14:36:17 +00:00
<tr> <td> <b> batouttotal </b> </td> <td> totale Batterieentladung ( Wh ) zu Beginn der Stunde </td> </tr>
2021-05-16 11:24:05 +00:00
<tr> <td> <b> batout </b> </td> <td> Batterieentladung der Stunde ( Wh ) </td> </tr>
2021-05-11 16:42:32 +00:00
<tr> <td> <b> wid </b> </td> <td> Identifikationsnummer des Wetters </td> </tr>
<tr> <td> <b> wcc </b> </td> <td> effektive Wolkenbedeckung </td> </tr>
2021-05-02 20:24:39 +00:00
<tr> <td> <b> wrp </b> </td> <td> Wahrscheinlichkeit von Niederschlag > 0 , 1 mm während der jeweiligen Stunde </td> </tr>
2021-05-11 16:42:32 +00:00
<tr> <td> <b> pvcorrf </b> </td> <td> abgeleiteter Autokorrekturfaktor </td> </tr>
2021-05-02 20:24:39 +00:00
<tr> <td> <b> csmtXX </b> </td> <td> gemessene Summe Energieverbrauch von ConsumerXX </td> </tr>
<tr> <td> <b> csmeXX </b> </td> <td> Energieverbrauch von ConsumerXX in der jeweiligen Stunde bzw . des Tages </td> </tr>
<tr> <td> <b> hourscsmeXX </b> </td> <td> gemessene Aktivstunden von ConsumerXX des Tages </td> </tr>
2021-03-23 16:49:43 +00:00
</table>
</ul>
2021-01-17 19:13:02 +00:00
</li>
</ul>
<br>
2021-03-18 22:12:08 +00:00
<ul>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-get-pvCircular" > </a>
2021-06-01 21:05:53 +00:00
<li> <b> pvCircular </b> <br> <br>
2021-04-06 15:50:16 +00:00
Listet die vorhandenen Werte im Ringspeicher auf .
2021-03-19 13:17:55 +00:00
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-23 16:49:43 +00:00
Erläuterung der Werte: <br> <br>
<ul>
<table>
2021-04-11 07:54:28 +00:00
<colgroup> < col width = 10 % > < col width = 90 % > < / colgroup >
<tr> <td> <b> pvfc </b> </td> <td> PV Vorhersage für die nächsten 24 h ab aktueller Stunde des Tages </td> </tr>
<tr> <td> <b> pvrl </b> </td> <td> reale PV Erzeugung der letzten 24 h ( Achtung: pvforecast und pvreal beziehen sich nicht auf den gleichen Zeitraum ! ) </td> </tr>
2021-05-12 11:12:59 +00:00
<tr> <td> <b> confc </b> </td> <td> erwarteter Energieverbrauch ( Wh ) </td> </tr>
2021-04-11 07:54:28 +00:00
<tr> <td> <b> gcon </b> </td> <td> realer Leistungsbezug aus dem Stromnetz </td> </tr>
<tr> <td> <b> gfeedin </b> </td> <td> reale Leistungseinspeisung in das Stromnetz </td> </tr>
2021-05-16 11:24:05 +00:00
<tr> <td> <b> batin </b> </td> <td> Batterieladung </td> </tr>
<tr> <td> <b> batout </b> </td> <td> Batterieentladung </td> </tr>
2021-04-11 07:54:28 +00:00
<tr> <td> <b> wcc </b> </td> <td> Grad der Wolkenüberdeckung </td> </tr>
<tr> <td> <b> wrp </b> </td> <td> Grad der Regenwahrscheinlichkeit </td> </tr>
2021-04-18 16:37:33 +00:00
<tr> <td> <b> temp </b> </td> <td> Außentemperatur </td> </tr>
2021-04-11 07:54:28 +00:00
<tr> <td> <b> wid </b> </td> <td> ID des vorhergesagten Wetters </td> </tr>
<tr> <td> <b> wtxt </b> </td> <td> Beschreibung des vorhergesagten Wetters </td> </tr>
2021-04-14 12:18:26 +00:00
<tr> <td> <b> corr </b> </td> <td> Autokorrekturfaktoren für die Stunde des Tages und der Bewölkungsrange ( 0 .. 10 ) </td> </tr>
<tr> <td> <b> quality </b> </td> <td> Qualität der Autokorrekturfaktoren ( max . 30 ) , höhere Werte = höhere Qualität </td> </tr>
2021-03-23 16:49:43 +00:00
</table>
</ul>
2021-03-17 20:06:19 +00:00
</li>
</ul>
<br>
2021-03-28 08:24:48 +00:00
<ul>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-get-valConsumerMaster" > </a>
2021-06-01 21:05:53 +00:00
<li> <b> valConsumerMaster </b> <br> <br>
2021-05-02 20:24:39 +00:00
Listet die aktuell ermittelten Stammdaten der im Device registrierten Verbraucher auf .
</li>
</ul>
<br>
<ul>
< a id = "SolarForecast-get-valCurrent" > </a>
2021-06-01 21:05:53 +00:00
<li> <b> valCurrent </b> <br> <br>
2021-03-28 08:24:48 +00:00
Listet die aktuell ermittelten Werte auf .
2021-03-17 20:06:19 +00:00
</li>
</ul>
<br>
2020-12-13 17:29:15 +00:00
</ul>
<br>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr" > </a>
2020-12-13 17:29:15 +00:00
<b> Attribute </b>
<br> <br>
<ul>
<ul>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-alias" > </a>
2021-04-05 06:20:53 +00:00
<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>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-autoRefresh" > </a>
2021-04-05 06:20:53 +00:00
<li> <b> autoRefresh </b> <br>
2020-12-13 17:29:15 +00:00
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>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-autoRefreshFW" > </a>
2020-12-13 17:29:15 +00:00
<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-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-beam1Color" > </a>
2021-03-14 11:51:38 +00:00
<li> <b> beam1Color </b> <br>
2020-12-13 17:29:15 +00:00
Farbauswahl der primären Balken .
</li>
<br>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-beam1Content" > </a>
2021-03-14 11:51:38 +00:00
<li> <b> beam1Content </b> <br>
Legt den darzustellenden Inhalt der primären Balken fest .
<ul>
<table>
2021-05-11 16:42:32 +00:00
<colgroup> < col width = 15 % > < col width = 85 % > < / colgroup >
2021-05-12 13:17:21 +00:00
<tr> <td> <b> pvForecast </b> </td> <td> prognostizierte PV - Erzeugung ( default ) </td> </tr>
<tr> <td> <b> pvReal </b> </td> <td> reale PV - Erzeugung </td> </tr>
2021-05-11 16:42:32 +00:00
<tr> <td> <b> gridconsumption </b> </td> <td> Energie Bezug aus dem Netz </td> </tr>
<tr> <td> <b> consumptionForecast </b> </td> <td> prognostizierter Energieverbrauch </td> </tr>
2021-03-14 11:51:38 +00:00
</table>
</ul>
</li>
<br>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-beam2Color" > </a>
2021-03-14 11:51:38 +00:00
<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-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-beam2Content" > </a>
2021-03-14 11:51:38 +00:00
<li> <b> beam2Content </b> <br>
Legt den darzustellenden Inhalt der sekundären Balken fest .
<ul>
<table>
2021-05-11 16:42:32 +00:00
<colgroup> < col width = 15 % > < col width = 85 % > < / colgroup >
2021-05-12 13:17:21 +00:00
<tr> <td> <b> pvForecast </b> </td> <td> prognostizierte PV - Erzeugung ( default ) </td> </tr>
<tr> <td> <b> pvReal </b> </td> <td> reale PV - Erzeugung </td> </tr>
2021-05-11 16:42:32 +00:00
<tr> <td> <b> gridconsumption </b> </td> <td> Energie Bezug aus dem Netz </td> </tr>
<tr> <td> <b> consumptionForecast </b> </td> <td> prognostizierter Energieverbrauch </td> </tr>
2021-03-14 11:51:38 +00:00
</table>
2021-05-11 16:42:32 +00:00
</ul>
2021-03-14 11:51:38 +00:00
</li>
<br>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-beamHeight" > </a>
2020-12-13 17:29:15 +00:00
<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>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-beamWidth" > </a>
2020-12-13 17:29:15 +00:00
<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-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-cloudFactorDamping" > </a>
2021-03-25 18:12:26 +00:00
<li> <b> cloudFactorDamping </b> <br>
Prozentuale Berücksichtigung ( Dämpfung ) des Bewölkungprognosefaktors bei der solaren Vorhersage . <br>
2021-03-21 14:20:02 +00:00
Größere Werte vermindern , kleinere Werte erhöhen tendenziell den prognostizierten PV Ertrag . <br>
2021-04-18 16:37:33 +00:00
( default: 35 )
2021-03-14 11:51:38 +00:00
</li>
2021-05-29 12:56:28 +00:00
<br>
2021-06-12 12:32:43 +00:00
< a id = "SolarForecast-attr-consumerAdviceIcon" > </a>
<li> <b> consumerAdviceIcon </b> <br>
2021-06-13 14:11:26 +00:00
Definiert die Art der Information über die geplanten Schaltzeiten eines Verbrauchers in der Verbraucherlegende .
<br> <br>
<ul>
<table>
<colgroup> < col width = 10 % > < col width = 90 % > < / colgroup >
<tr> <td> <b> & lt ; Icon & gt @ & lt ; Farbe & gt </b> </td> <td> Aktivierungsempfehlung wird durch Icon und Farbe ( optional ) dargestellt ( default: light_light_dim_100 @ gold ) </td> </tr>
<tr> <td> </td> <td> ( die Planungsdaten werden als Mouse - Over Text angezeigt </td> </tr>
<tr> <td> <b> times </b> </td> <td> es werden der Planungsstatus und die geplanten Schaltzeiten als Text angezeigt </td> </tr>
<tr> <td> <b> none </b> </td> <td> keine Anzeige der Planungsdaten </td> </tr>
</table>
</ul>
2021-06-12 12:32:43 +00:00
</li>
<br>
2021-05-29 12:56:28 +00:00
< a id = "SolarForecast-attr-consumerLegend" > </a>
<li> <b> consumerLegend </b> <br>
Definiert die Lage bzw . Darstellungsweise der Verbraucherlegende sofern Verbraucher SolarForecast Device
registriert sind . <br>
( default: icon_top )
</li>
<br>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-consumer" data - pattern = "consumer.*" > </a>
2021-06-01 21:05:53 +00:00
<li> <b> consumerXX & lt ; Device Name & gt ; type = & lt ; type & gt ; power = & lt ; power & gt ; [ mode = & lt ; mode & gt ; ] [ icon = & lt ; Icon & gt ; ] [ mintime = & lt ; minutes & gt ; ] [ on = & lt ; Kommando & gt ; ] [ off = & lt ; Kommando & gt ; ] [ notbefore = & lt ; Stunde & gt ; ] [ notafter = & lt ; Stunde & gt ; ] [ auto = & lt ; Readingname & gt ; ] [ etotal = & lt ; Readingname & gt ; : & lt ; Einheit & gt ; ] </b> <br> <br>
2021-05-02 20:24:39 +00:00
Registriert einen Verbraucher & lt ; Device Name & gt ; beim SolarForecast Device . Dabei ist & lt ; Device Name & gt ;
ein in FHEM bereits angelegtes Verbraucher Device , z . B . eine Schaltsteckdose . Die meisten Schlüssel sind optional ,
2021-05-30 07:54:58 +00:00
sind aber für bestimmte Funktionalitäten Voraussetzung und werden mit default - Werten besetzt . <br>
2021-06-04 09:19:18 +00:00
Ist der Schüssel "auto" definiert , kann der Automatikmodus in der integrierten Verbrauchergrafik mit den
2021-06-04 09:52:45 +00:00
entsprechenden Drucktasten umgeschaltet werden . Das angegebene Reading wird ggf . im Consumer Device angelegt falls
es nicht vorhanden ist .
2021-05-30 07:54:58 +00:00
<br> <br>
2021-05-02 20:24:39 +00:00
<ul>
<table>
<colgroup> < col width = 10 % > < col width = 90 % > < / colgroup >
2021-06-01 21:05:53 +00:00
<tr> <td> <b> type </b> </td> <td> Typ des Verbrauchers . Folgende Typen sind erlaubt: </td> </tr>
<tr> <td> </td> <td> <b> dishwasher </b> - Verbaucher ist eine Spülmschine </td> </tr>
<tr> <td> </td> <td> <b> dryer </b> - Verbaucher ist ein Wäschetrockner </td> </tr>
<tr> <td> </td> <td> <b> washingmachine </b> - Verbaucher ist eine Waschmaschine </td> </tr>
<tr> <td> </td> <td> <b> heater </b> - Verbaucher ist ein Heizstab </td> </tr>
<tr> <td> </td> <td> <b> other </b> - Verbraucher ist keiner der vorgenannten Typen </td> </tr>
<tr> <td> <b> power </b> </td> <td> typische Leistungsaufnahme des Verbrauchers ( siehe Datenblatt ) in W </td> </tr>
<tr> <td> <b> mode </b> </td> <td> Planungsmodus des Verbrauchers ( optional ) . Erlaubt sind: </td> </tr>
<tr> <td> </td> <td> <b> can </b> - der Verbaucher kann angeschaltet werden wenn genügend Energie bereitsteht ( default ) </td> </tr>
<tr> <td> </td> <td> <b> must </b> - der Verbaucher muß einmal am Tag angeschaltet werden auch wenn nicht genügend Energie vorhanden ist </td> </tr>
<tr> <td> <b> icon </b> </td> <td> Icon zur Darstellung des Verbrauchers in der Übersichtsgrafik ( optional ) </td> </tr>
<tr> <td> <b> mintime </b> </td> <td> Mindestlaufzeit bzw . typische Laufzeit für einen Zyklus des Verbrauchers nach dem Einschalten in Minuten ( default: Typ bezogen ) </td> </tr>
<tr> <td> <b> on </b> </td> <td> Set - Kommando zum Einschalten des Verbrauchers ( optional ) </td> </tr>
<tr> <td> <b> off </b> </td> <td> Set - Kommando zum Ausschalten des Verbrauchers ( optional ) </td> </tr>
<tr> <td> <b> notbefore </b> </td> <td> Verbraucher nicht vor angegebener Stunde ( 01 .. 23 ) einschalten ( optional ) </td> </tr>
2021-06-13 09:43:58 +00:00
<tr> <td> <b> notafter </b> </td> <td> Verbraucher nicht nach angegebener Stunde ( 01 .. 23 ) einschalten ( optional ) </td> </tr>
2021-06-01 21:05:53 +00:00
<tr> <td> <b> auto </b> </td> <td> Reading im Verbraucherdevice welches das Schalten des Verbrauchers freigibt bzw . blockiert ( optional ) </td> </tr>
<tr> <td> </td> <td> Readingwert = 1 : Schalten freigegeben ( default ) , 0 : Schalten blockiert </td> </tr>
<tr> <td> <b> etotal </b> </td> <td> Reading welches die Summe der verbrauchten Energie liefert und der Einheit ( Wh /kWh) (optional) </ td > </tr>
2021-05-02 20:24:39 +00:00
</table>
</ul>
<br>
<ul>
2021-06-15 19:43:20 +00:00
<b> Beispiele: </b> <br>
attr consumer01 wallplug icon = scene_dishwasher @ orange type = dishwasher mode = can power = 2500 on = on off = off notafter = 20 etotal = total:kWh <br>
attr consumer02 WPxw type = heater mode = can power = 3000 mintime = 180 on = "on-for-timer 3600" notafter = 12
2021-05-02 20:24:39 +00:00
</ul>
</li>
<br>
2021-06-12 07:03:47 +00:00
< a id = "SolarForecast-attr-Css" > </a>
<li> <b> Css </b> <br>
2021-06-12 08:37:50 +00:00
Definiert den Style für die Energieflußgrafik . Das Attribut wird automatisch vorbelegt .
Zum Ändern des Css - Attributes bitte den Default übernehmen und anpassen: <br> <br>
<ul>
. flowg . text { stroke: none ; fill: gray ; } <br>
. flowg . sun_active { stroke: orange ; fill: orange ; } <br>
. flowg . sun_inactive { stroke: gray ; fill: gray ; } <br>
. flowg . bat25 { stroke: red ; fill: red ; } <br>
. flowg . bat50 { stroke: yellow ; fill: yellow ; } <br>
. flowg . bat75 { stroke: green ; fill: green ; } <br>
. flowg . grid_color1 { fill: green ; } <br>
. flowg . grid_color2 { fill: red ; } <br>
. flowg . grid_color3 { fill: gray ; } <br>
. flowg . inactive_in { stroke: gray ; stroke - dashoffset: 20 ; stroke - dasharray: 10 ; opacity: 0.2 ; } <br>
. flowg . inactive_out { stroke: gray ; stroke - dashoffset: 20 ; stroke - dasharray: 10 ; opacity: 0.2 ; } <br>
. flowg . active_in { stroke: red ; stroke - dashoffset: 20 ; stroke - dasharray: 10 ; opacity: 0.8 ; animation: dash 0.5 s linear ; animation - iteration - count: infinite ; } <br>
. flowg . active_out { stroke: yellow ; stroke - dashoffset: 20 ; stroke - dasharray: 10 ; opacity: 0.8 ; animation: dash 0.5 s linear ; animation - iteration - count: infinite ; } <br>
</ul>
2021-06-12 07:03:47 +00:00
</li>
<br>
2020-12-13 17:29:15 +00:00
2021-06-13 09:43:58 +00:00
< a id = "SolarForecast-attr-debug" > </a>
<li> <b> debug </b> <br>
Aktiviert / deaktiviert Debug - Meldungen im Modul .
</li>
<br>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-disable" > </a>
2020-12-13 17:29:15 +00:00
<li> <b> disable </b> <br>
Aktiviert / deaktiviert das Device .
</li>
<br>
2021-04-25 15:33:22 +00:00
2021-05-28 08:40:57 +00:00
< a id = "SolarForecast-attr-flowGraphicAnimate" > </a>
<li> <b> flowGraphicAnimate </b> <br>
Animiert die Energieflußgrafik sofern angezeigt .
Siehe auch Attribut < a href = "#SolarForecast-attr-graphicSelect" > graphicSelect </a> . <br>
( default: 0 )
</li>
<br>
2021-05-27 20:23:40 +00:00
< a id = "SolarForecast-attr-flowGraphicSize" > </a>
<li> <b> flowGraphicSize & lt ; Pixel & gt ; </b> <br>
2021-05-28 08:40:57 +00:00
Größe der Energieflußgrafik sofern angezeigt .
Siehe auch Attribut < a href = "#SolarForecast-attr-graphicSelect" > graphicSelect </a> . <br>
2021-05-27 20:23:40 +00:00
( default: 300 )
</li>
<br>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-follow70percentRule" > </a>
2021-04-25 15:33:22 +00:00
<li> <b> follow70percentRule </b> <br>
2021-04-27 20:48:54 +00:00
Wenn gesetzt , wird die prognostizierte Leistung entsprechend der 70 % Regel begrenzt . <br> <br>
<ul>
<table>
<colgroup> < col width = 15 % > < col width = 85 % > < / colgroup >
2021-04-28 16:09:16 +00:00
<tr> <td> <b> 0 </b> </td> <td> keine Begrenzung der prognostizierten PV - Erzeugung ( default ) </td> </tr>
<tr> <td> <b> 1 </b> </td> <td> die prognostizierte PV - Erzeugung wird auf 70 % der installierten Stringleistung ( en ) begrenzt </td> </tr>
<tr> <td> <b> dynamic </b> </td> <td> die prognostizierte PV - Erzeugung wird begrenzt wenn 70 % der installierten </td> </tr>
<tr> <td> </td> <td> Stringleistung ( en ) zzgl . des prognostizierten Verbrauchs überschritten wird </td> </tr>
2021-04-27 20:48:54 +00:00
</table>
</ul>
2021-04-25 15:33:22 +00:00
</li>
<br>
2020-12-13 17:29:15 +00:00
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-forcePageRefresh" > </a>
2020-12-13 17:29:15 +00:00
<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>
2021-05-27 20:23:40 +00:00
< a id = "SolarForecast-attr-graphicSelect" > </a>
<li> <b> graphicSelect </b> <br>
2021-06-12 07:03:47 +00:00
Wählt die anzuzeigende interne Grafik des Moduls aus . <br>
Zur Anpassung der Energieflußgrafik steht das Attribut < a href = "#SolarForecast-attr-Css" > Css </a> zur
Verfügung . <br> <br>
2021-05-27 20:23:40 +00:00
<ul>
<table>
<colgroup> < col width = 15 % > < col width = 85 % > < / colgroup >
<tr> <td> <b> flow </b> </td> <td> zeigt die Energieflußgrafik an </td> </tr>
2021-06-10 20:32:37 +00:00
<tr> <td> <b> forecast </b> </td> <td> zeigt die Vorhersagegrafik an </td> </tr>
2021-05-27 20:23:40 +00:00
<tr> <td> <b> both </b> </td> <td> zeigt Energiefluß - und Vorhersagegrafik an ( default ) </td> </tr>
2021-06-10 20:32:37 +00:00
<tr> <td> <b> none </b> </td> <td> es wird keine Grafik angezeigt </td> </tr>
2021-05-27 20:23:40 +00:00
</table>
</ul>
</li>
<br>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-hourCount" > </a>
2021-01-20 21:00:34 +00:00
<li> <b> hourCount & lt ; 4 ... 24 & gt ; </b> <br>
Anzahl der Balken / Stunden . <br>
( default: 24 )
</li>
<br>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-headerDetail" > </a>
2021-04-04 21:07:38 +00:00
<li> <b> headerDetail </b> <br>
2020-12-13 17:29:15 +00:00
Detailiierungsgrad der Kopfzeilen . <br>
( default: all )
<ul>
<table>
2021-04-04 21:07:38 +00:00
<colgroup> < col width = 10 % > < col width = 90 % > < / colgroup >
2020-12-13 17:29:15 +00:00
<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>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-hourStyle" > </a>
2020-12-13 17:29:15 +00:00
<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>
2021-04-09 16:11:17 +00:00
<colgroup> < col width = 30 % > < col width = 70 % > < / 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-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-htmlStart" > </a>
2021-01-20 21:00:34 +00:00
<li> <b> htmlStart & lt ; HTML - String & gt ; </b> <br>
Angabe eines beliebigen HTML - Strings der vor dem Grafik - Code ausgeführt wird .
</li>
<br>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-htmlEnd" > </a>
2021-01-20 21:00:34 +00:00
<li> <b> htmlEnd & lt ; HTML - String & gt ; </b> <br>
Angabe eines beliebigen HTML - Strings der nach dem Grafik - Code ausgeführt wird .
</li>
<br>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-interval" > </a>
2020-12-15 13:41:10 +00:00
<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
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-layoutType" > </a>
2021-03-25 17:38:19 +00:00
<li> <b> layoutType & lt ; single | double | diff & gt ; </b> <br>
2021-03-14 11:51:38 +00:00
Layout der integrierten Grafik . <br>
2021-03-25 17:38:19 +00:00
Der darzustellende Inhalt der Balken wird durch die Attribute <b> beam1Content </b> bzw . <b> beam2Content </b>
bestimmt . <br>
( default: single )
2021-03-14 11:51:38 +00:00
<br> <br>
<ul>
<table>
2021-03-25 17:38:19 +00:00
<colgroup> < col width = 5 % > < col width = 95 % > < / colgroup >
2021-05-30 18:37:08 +00:00
<tr> <td> <b> single </b> </td> <td> - zeigt nur den primären Balken an </td> </tr>
<tr> <td> <b> double </b> </td> <td> - zeigt den primären Balken und den sekundären Balken an </td> </tr>
2021-03-25 17:38:19 +00:00
<tr> <td> <b> diff </b> </td> <td> - Differenzanzeige . Es gilt: & lt ; Differenz & gt ; = & lt ; Wert primärer Balken & gt ; - & lt ; Wert sekundärer Balken & gt ; </td> </tr>
2021-03-14 11:51:38 +00:00
</table>
</ul>
</li>
<br>
2020-12-13 17:29:15 +00:00
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-maxValBeam" > </a>
2021-04-02 06:37:48 +00:00
<li> <b> maxValBeam & lt ; 0 ... val & gt ; </b> <br>
Festlegung des maximalen Betrags des primären Balkens ( Stundenwert ) zur Berechnung der maximalen Balkenhöhe .
Dadurch erfolgt eine Anpassung der zulässigen Gesamthöhe der Grafik . <br>
Wenn nicht gesetzt oder 0 , erfolgt eine dynamische Anpassung . <br>
( default: 0 )
2020-12-13 17:29:15 +00:00
</li>
<br>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-maxVariancePerDay" > </a>
2020-12-27 20:04:17 +00:00
<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-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-numHistDays" > </a>
2021-01-20 21:00:34 +00:00
<li> <b> numHistDays </b> <br>
2021-04-14 12:18:26 +00:00
Anzahl der historischen Tage die zur Autokorrektur der PV Vorhersage verwendet werden sofern
2021-03-18 10:06:40 +00:00
aktiviert . <br>
2021-04-14 12:18:26 +00:00
( default: 30 )
2020-12-13 17:29:15 +00:00
</li>
<br>
2021-03-20 14:14:18 +00:00
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-rainFactorDamping" > </a>
2021-03-25 18:12:26 +00:00
<li> <b> rainFactorDamping </b> <br>
Prozentuale Berücksichtigung ( Dämpfung ) des Regenprognosefaktors bei der solaren Vorhersage . <br>
2021-03-21 14:20:02 +00:00
Größere Werte vermindern , kleinere Werte erhöhen tendenziell den prognostizierten PV Ertrag . <br>
2021-04-18 16:37:33 +00:00
( default: 10 )
2021-03-20 14:14:18 +00:00
</li>
<br>
2021-04-24 07:37:41 +00:00
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-sameWeekdaysForConsfc" > </a>
2021-04-24 07:37:41 +00:00
<li> <b> sameWeekdaysForConsfc </b> <br>
Wenn gesetzt , werden zur Berechnung der Verbrauchsprognose nur gleiche Wochentage ( Mo .. So ) einbezogen . <br>
Anderenfalls werden alle Wochentage gleichberechtigt zur Kalkulation verwendet . <br>
( default: 0 )
</li>
<br>
2020-12-13 17:29:15 +00:00
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-showDiff" > </a>
2020-12-13 17:29:15 +00:00
<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>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-showHeader" > </a>
2020-12-13 17:29:15 +00:00
<li> <b> showHeader </b> <br>
Anzeige der Kopfzeile mit Prognosedaten , Rest des aktuellen Tages und des nächsten Tages <br>
( default: 1 )
</li>
<br>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-showLink" > </a>
2020-12-13 17:29:15 +00:00
<li> <b> showLink </b> <br>
Anzeige des Detail - Links über dem Grafik - Device <br>
( default: 1 )
</li>
<br>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-showNight" > </a>
2020-12-13 17:29:15 +00:00
<li> <b> showNight </b> <br>
Die Nachtstunden ( ohne Ertragsprognose ) werden mit angezeigt . <br>
( default: 0 )
</li>
<br>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-showWeather" > </a>
2020-12-13 17:29:15 +00:00
<li> <b> showWeather </b> <br>
Wettericons anzeigen . <br>
( default: 1 )
</li>
<br>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-spaceSize" > </a>
2020-12-13 17:29:15 +00:00
<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>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-Wh/kWh" > </a>
2020-12-13 17:29:15 +00:00
<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>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-weatherColor" > </a>
2020-12-13 17:29:15 +00:00
<li> <b> weatherColor </b> <br>
Farbe der Wetter - Icons .
</li>
2021-01-01 19:44:20 +00:00
<br>
2021-05-02 20:24:39 +00:00
< a id = "SolarForecast-attr-weatherColorNight" > </a>
2021-03-26 12:03:05 +00:00
<li> <b> weatherColorNight </b> <br>
2021-01-01 19:44:20 +00:00
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
{
2021-05-17 18:39:29 +00:00
"abstract" : "Creation of solar predictions for PV systems" ,
2020-12-13 17:29:15 +00:00
"x_lang" : {
"de" : {
2021-05-17 18:39:29 +00:00
"abstract" : "Erstellung solarer Vorhersagen von PV Anlagen"
2020-12-13 17:29:15 +00:00
}
} ,
"keywords" : [
2021-05-17 18:39:29 +00:00
"inverter" ,
2020-12-13 17:29:15 +00:00
"photovoltaik" ,
"electricity" ,
2021-05-17 18:39:29 +00:00
"forecast" ,
2020-12-13 17:29:15 +00:00
"graphics" ,
2021-05-17 18:39:29 +00:00
"Autarky" ,
"Consumer" ,
"PV"
2020-12-13 17:29:15 +00:00
] ,
"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 ,
2021-05-17 18:39:29 +00:00
"POSIX" : 0 ,
"GPUtils" : 0 ,
"Encode" : 0 ,
"utf8" : 0 ,
"JSON" : 4.020 ,
"Data::Dumper" : 0 ,
"FHEM::SynoModules::SMUtils" : 1.220 ,
2020-12-13 17:29:15 +00:00
"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