diff --git a/fhem/contrib/DS_Starter/76_SolarForecast.pm b/fhem/contrib/DS_Starter/76_SolarForecast.pm index f909b7933..40b3b8d0b 100644 --- a/fhem/contrib/DS_Starter/76_SolarForecast.pm +++ b/fhem/contrib/DS_Starter/76_SolarForecast.pm @@ -1,5 +1,5 @@ ######################################################################################################################## -# $Id: 76_SolarForecast.pm 21735 2023-07-14 23:53:24Z DS_Starter $ +# $Id: 76_SolarForecast.pm 21735 2023-07-16 23:53:24Z DS_Starter $ ######################################################################################################################### # 76_SolarForecast.pm # @@ -136,6 +136,9 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "0.80.12"=> "16.07.2023 preparation for alternative switch device in consumer attribute, revise CommandRef ". + "fix/improve sub ___readCandQ and much more, get pvHistory -> one specific day selectable ". + "get valConsumerMaster -> one specific consumer selectable, enhance consumer key locktime by on-locktime ", "0.80.11"=> "14.07.2023 minor fixes and improvements ", "0.80.10"=> "13.07.2023 new key spignorecond in consumer attributes ", "0.80.9" => "13.07.2023 new method of prediction quality calculation -> sub __calcFcQuality, minor bug fixes ", @@ -2088,6 +2091,8 @@ sub Get { my $opt = shift @a; my $arg = join " ", map { my $p = $_; $p =~ s/\s//xg; $p; } @a; ## no critic 'Map blocks' + my $type = $hash->{TYPE}; + my @ho = qw (both both_noHead both_noCons @@ -2103,16 +2108,21 @@ sub Get { none ); + my @pha = map {sprintf "%02d", $_} sort {$a<=>$b} keys %{$data{$type}{$name}{pvhist}}; + my @vcm = map {sprintf "%02d", $_} sort {$a<=>$b} keys %{$data{$type}{$name}{consumers}}; + my $hol = join ",", @ho; + my $pvl = join ",", @pha; + my $cml = join ",", @vcm; my $getlist = "Unknown argument $opt, choose one of ". - "valConsumerMaster:noArg ". + "valConsumerMaster:#,$cml ". "data:noArg ". "forecastQualities:noArg ". "html:$hol ". "nextHours:noArg ". "pvCircular:noArg ". - "pvHistory:noArg ". + "pvHistory:#,$pvl ". "rooftopData:noArg ". "solApiData:noArg ". "valCurrent:noArg " @@ -2123,7 +2133,7 @@ sub Get { my $params = { hash => $hash, name => $name, - type => $hash->{TYPE}, + type => $type, opt => $opt, arg => $arg }; @@ -2154,7 +2164,7 @@ sub _getRoofTopData { $paref->{date} = strftime "%Y-%m-%d", localtime(time); # aktuelles Datum $paref->{debug} = AttrVal ($name, 'ctrlDebug', 'none'); - $paref->{lang} = AttrVal ($name, 'ctrlLanguage', AttrVal ('global', 'language', $deflang)); + $paref->{lang} = getLang ($hash); if($hash->{MODEL} eq 'SolCastAPI') { __getSolCastData ($paref); @@ -3063,8 +3073,9 @@ return pageAsHtml ($name, "ftui"); sub _getlistPVHistory { my $paref = shift; my $hash = $paref->{hash}; + my $arg = $paref->{arg}; - my $ret = listDataPool ($hash, "pvhist"); + my $ret = listDataPool ($hash, 'pvhist', $arg); return $ret; } @@ -3076,7 +3087,7 @@ sub _getlistPVCircular { my $paref = shift; my $hash = $paref->{hash}; - my $ret = listDataPool ($hash, "circular"); + my $ret = listDataPool ($hash, 'circular'); return $ret; } @@ -3088,7 +3099,7 @@ sub _getlistNextHours { my $paref = shift; my $hash = $paref->{hash}; - my $ret = listDataPool ($hash, "nexthours"); + my $ret = listDataPool ($hash, 'nexthours'); return $ret; } @@ -3100,7 +3111,7 @@ sub _getForecastQualities { my $paref = shift; my $hash = $paref->{hash}; - my $ret = listDataPool ($hash, "qualities"); + my $ret = listDataPool ($hash, 'qualities'); return $ret; } @@ -3112,7 +3123,7 @@ sub _getlistCurrent { my $paref = shift; my $hash = $paref->{hash}; - my $ret = listDataPool ($hash, "current"); + my $ret = listDataPool ($hash, 'current'); return $ret; } @@ -3123,8 +3134,9 @@ return $ret; sub _getlistvalConsumerMaster { my $paref = shift; my $hash = $paref->{hash}; + my $arg = $paref->{arg}; - my $ret = listDataPool ($hash, "consumer"); + my $ret = listDataPool ($hash, 'consumer', $arg); return $ret; } @@ -3136,7 +3148,7 @@ sub _getlistSolCastData { my $paref = shift; my $hash = $paref->{hash}; - my $ret = listDataPool ($hash, "solApiData"); + my $ret = listDataPool ($hash, 'solApiData'); return $ret; } @@ -3255,7 +3267,15 @@ sub _attrconsumer { ## no critic "not used" my $alowt = $h->{type} ~~ @ctypes ? 1 : 0; if (!$alowt) { - return qq{The type "$h->{type}" isn't allowed!}; + return qq{The type "$h->{type}" isn't allowed!}; + } + + if (exists $h->{switchdev}) { + my $dswitch = $h->{switchdev}; # alternatives Schaltdevice + + if(!$defs{$dswitch}) { + return qq{The device "$dswitch" doesn't exist!}; + } } if ($h->{power} !~ /^[0-9]+$/xs) { @@ -3328,7 +3348,8 @@ sub _attrconsumer { ## no critic "not used" writeCacheToFile ($hash, "consumers", $csmcache.$name); # Cache File Consumer schreiben - InternalTimer(gettimeofday()+2, "FHEM::SolarForecast::createAssociatedWith", $hash, 0); + InternalTimer(gettimeofday()+0.5, 'FHEM::SolarForecast::centralTask', [$name, 0], 0); + InternalTimer(gettimeofday()+2, 'FHEM::SolarForecast::createAssociatedWith', $hash, 0); return; } @@ -3381,30 +3402,50 @@ sub Notify { my $events = deviceEvents($dev_hash, 1); return if(!$events); - my $cdref = CurrentVal ($myHash, 'consumerdevs', ''); # alle registrierten Consumer + my $cdref = CurrentVal ($myHash, 'consumerdevs', ''); # alle registrierten Consumer und Schaltdevices my @consumers = (); @consumers = @{$cdref} if(ref $cdref eq "ARRAY"); return if(!@consumers); + + my $debug = AttrVal ($myName, 'ctrlDebug', 'none'); # Debug Mode if($devName ~~ @consumers) { - my $cindex; + my ($cname, $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) { + $cname = ConsumerVal ($myHash, $c, 'name', ''); + + if ($devName eq $cname) { $cindex = $c; last; } + + my $dswname = ConsumerVal ($myHash, $c, 'dswitch', ''); # Switch Device + + if ($devName eq $dswname) { + $cindex = $c; + $cname = ConsumerVal ($myHash, $c, 'name', ''); + + if ($debug =~ /notifyHandling/x) { + Log3 ($myName, 1, qq{$myName DEBUG> notifyHandling - Event device >$devName< is switching device of consumer >$cname< (index: $c)}); + } + + last; + } } - my $debug = AttrVal ($myName, 'ctrlDebug', 'none'); # Debug Mode - my $async = ConsumerVal ($myHash, $cindex, 'asynchron', 0); - my $rswstate = ConsumerVal ($myHash, $cindex, 'rswstate', ''); + if (!$cindex) { + Log3 ($myName, 2, qq{$myName notifyHandling - Device >$devName< has no consumer index and/or ist not a known switching device. Exiting...}); + return; + } + + my $async = ConsumerVal ($myHash, $cindex, 'asynchron', 0); + my $rswstate = ConsumerVal ($myHash, $cindex, 'rswstate', 'state'); if ($debug =~ /notifyHandling/x) { - Log3 ($myName, 1, qq{$myName DEBUG> Event received - Consumer Device: $devName, asynchronous: $async}); + Log3 ($myName, 1, qq{$myName DEBUG> notifyHandling - Consumer >$cindex< asynchronous mode: $async}); } return if(!$async); # Consumer synchron -> keine Weiterverarbeitung @@ -3441,7 +3482,7 @@ sub Notify { if ($reading eq $rswstate) { if ($debug =~ /notifyHandling/x) { - Log3 ($myName, 1, qq{$myName DEBUG> start centralTask by Notify - $devName: $reading $value}); + Log3 ($myName, 1, qq{$myName DEBUG> notifyHandling - start centralTask by Notify device: $devName, reading: $reading, value: $value}); } centralTask ($myHash, 0); # keine Events in SolarForeCast außer 'state' @@ -3673,12 +3714,26 @@ return @pvconf; # Zentraler Datenabruf ################################################################ sub centralTask { - my $hash = shift; - my $evt = shift // 1; # Readings Event generieren + my $par = shift; + my $evt = shift // 1; # Readings Event generieren + + my ($hash, $name); + if (ref $par eq 'HASH') { # Standard Fn Aufruf + $hash = $par; + $name = $hash->{NAME}; + } + elsif (ref $par eq 'ARRAY') { # Array Referenz wurde übergeben + $name = $par->[0]; + $evt = $par->[1] // 1; # Readings Event generieren + $hash = $defs{$name}; + } + else { + Log (1, "ERROR module ".__PACKAGE__." - function >centralTask< was called with wrong data reference type: >".(ref $par)."<"); + return; + } - my $name = $hash->{NAME}; my $type = $hash->{TYPE}; - my $cst = [gettimeofday]; # Zyklus-Startzeit + my $cst = [gettimeofday]; # Zyklus-Startzeit RemoveInternalTimer($hash, "FHEM::SolarForecast::centralTask"); RemoveInternalTimer($hash, "FHEM::SolarForecast::singleUpdateState"); @@ -4361,7 +4416,7 @@ sub __calcPVestimates { my $acu = isAutoCorrUsed ($name); $paref->{cloudcover} = $cloudcover; - my ($hc, $hq) = ___readCorrf ($paref); # liest den anzuwendenden Korrekturfaktor + my ($hc, $hq) = ___readCandQ ($paref); # liest den anzuwendenden Korrekturfaktor delete $paref->{cloudcover}; my ($lh,$sq,$peakloss, $modtemp); @@ -4465,7 +4520,7 @@ return $pvsum; # Liest Korrekturfaktor/Qualität aus pvCircular percentile und # speichert die Werte im Nexthours / pvHistory Hash ###################################################################### -sub ___readCorrf { +sub ___readCandQ { my $paref = shift; my $hash = $paref->{hash}; my $name = $paref->{name}; @@ -4478,32 +4533,25 @@ sub ___readCorrf { my $acu = isAutoCorrUsed ($name); # Autokorrekturmodus my $pvcorr = ReadingsNum ($name, "pvCorrectionFactor_".sprintf("%02d",$fh1), 1.00); # PV Korrekturfaktor (auto oder manuell) my $hc = $pvcorr; # Voreinstellung RAW-Korrekturfaktor - my $hq = "m"; # Qualität manuell voreingestellt + my $hq = '-'; # keine Qualität definiert if ($acu =~ /on_complex/xs) { # Autokorrektur complex soll genutzt werden my $range = calcRange ($cc); # Range errechnen - ($hc, $hq) = CircularAutokorrVal ($hash, sprintf("%02d",$fh1), $range, undef); # Korrekturfaktor/KF-Qualität der Stunde des Tages der entsprechenden Bewölkungsrange - $hq //= 0; - - if (!defined $hc) { - $hc = 1; # Korrekturfaktor = 1 (keine Korrektur) - $hq = 0; - } + ($hc, $hq) = CircularAutokorrVal ($hash, sprintf("%02d",$fh1), $range, undef); # Korrekturfaktor/Qualität der Stunde des Tages (complex) + $hq //= '-'; + $hc //= 1; # Korrekturfaktor = 1 (keine Korrektur) # keine Qualität definiert $data{$type}{$name}{nexthours}{"NextHour".sprintf("%02d",$num)}{cloudrange} = $range; } elsif ($acu =~ /on_simple/xs) { - ($hc, $hq) = CircularAutokorrVal ($hash, sprintf("%02d",$fh1), 'percentile', undef); # Korrekturfaktor/KF-Qualität der Stunde des Tages der entsprechenden Bewölkungsrange - $hq //= 0; - - if (!defined $hc) { - $hc = 1; # Korrekturfaktor = 1 - $hq = 0; - } + ($hc, $hq) = CircularAutokorrVal ($hash, sprintf("%02d",$fh1), 'percentile', undef); # Korrekturfaktor/Qualität der Stunde des Tages (simple) + $hq //= '-'; + $hc //= 1; # Korrekturfaktor = 1 } - else { - $hc = 1; - $hq = "-"; + else { # keine Autokorrektur + ($hc, $hq) = CircularAutokorrVal ($hash, sprintf("%02d",$fh1), 'percentile', undef); # Korrekturfaktor/Qualität der Stunde des Tages (simple) + $hc = 1; + $hq //= '-'; } $hc = sprintf "%.2f", $hc; @@ -5111,13 +5159,13 @@ sub _manageConsumerData { if ($dnum) { if($consumerco) { - $data{$type}{$name}{consumers}{$c}{avgenergy} = ceil ($consumerco/$runhours); # Durchschnittsverbrauch pro Stunde in Wh + $data{$type}{$name}{consumers}{$c}{avgenergy} = sprintf "%.2f", ($consumerco/$runhours); # Durchschnittsverbrauch pro Stunde in Wh } else { delete $data{$type}{$name}{consumers}{$c}{avgenergy}; } - $data{$type}{$name}{consumers}{$c}{avgruntime} = (ceil($runhours/$dnum)) * 60; # Durchschnittslaufzeit am Tag in Minuten + $data{$type}{$name}{consumers}{$c}{avgruntime} = sprintf "%.2f", (($runhours / $dnum) * 60); # Durchschnittslaufzeit am Tag in Minuten } $paref->{consumer} = $c; @@ -5615,15 +5663,20 @@ sub ___setConsumerPlanningState { my $ps = $paref->{ps}; # Planstatus my $startts = $paref->{startts}; # Unix Timestamp für geplanten Switch on my $stopts = $paref->{stopts}; # Unix Timestamp für geplanten Switch off + my $lonts = $paref->{lastAutoOnTs}; # Timestamp des letzten On-Schaltens bzw. letzter Fortsetzung im Automatikmodus my $loffts = $paref->{lastAutoOffTs}; # Timestamp des letzten Off-Schaltens bzw. letzter Unterbrechnung im Automatikmodus my $lang = $paref->{lang}; my ($starttime,$stoptime); + if (defined $lonts) { + $data{$type}{$name}{consumers}{$c}{lastAutoOnTs} = $lonts; + } + if (defined $loffts) { $data{$type}{$name}{consumers}{$c}{lastAutoOffTs} = $loffts; } - + if ($startts) { $starttime = (timestampToTimestring ($startts, $lang))[3]; $data{$type}{$name}{consumers}{$c}{planswitchon} = $startts; @@ -5853,12 +5906,12 @@ sub ___switchConsumerOn { my $state = $paref->{state}; my $debug = $paref->{debug}; - my $pstate = ConsumerVal ($hash, $c, "planstate", ""); - my $startts = ConsumerVal ($hash, $c, "planswitchon", undef); # geplante Unix Startzeit - my $oncom = ConsumerVal ($hash, $c, "oncom", ""); # Set Command für "on" - my $auto = ConsumerVal ($hash, $c, "auto", 1); - my $cname = ConsumerVal ($hash, $c, "name", ""); # Consumer Device Name - my $calias = ConsumerVal ($hash, $c, "alias", ""); # Consumer Device Alias + my $pstate = ConsumerVal ($hash, $c, 'planstate', ''); + my $startts = ConsumerVal ($hash, $c, 'planswitchon', undef); # geplante Unix Startzeit + my $oncom = ConsumerVal ($hash, $c, 'oncom', ''); # Set Command für "on" + my $auto = ConsumerVal ($hash, $c, 'auto', 1); + my $cname = ConsumerVal ($hash, $c, 'name', ''); # Consumer Device Name + my $calias = ConsumerVal ($hash, $c, 'alias', ''); # Consumer Device Alias my ($swoncond,$swoffcond,$info,$err); ($swoncond,$info,$err) = isAddSwitchOnCond ($hash, $c); # zusätzliche Switch on Bedingung @@ -5873,17 +5926,24 @@ sub ___switchConsumerOn { my $cons = CurrentVal ($hash, 'consumption', 0); my $nompow = ConsumerVal ($hash, $c, 'power', '-'); my $sp = CurrentVal ($hash, 'surplus', 0); + my $lang = $paref->{lang}; + Log3 ($name, 1, qq{$name DEBUG> ############### consumer "$c" ############### }); Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - general switching parameters => }. qq{auto mode: $auto, current Consumption: $cons W, nompower: $nompow, surplus: $sp W, }. - qq{isInLocktime: $iilt, planning state: $pstate, start timestamp: }.($startts ? $startts : "undef") + qq{planstate: $pstate, starttime: }.($startts ? (timestampToTimestring ($startts, $lang))[0] : "undef") ); + Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - isInLocktime: $iilt}.($rlt ? ", remainLockTime: $rlt" : '')); Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - current Context is switching "on" => }. qq{swoncond: $swoncond, on-command: $oncom } ); + + if (simplifyCstate($pstate) =~ /planned|priority|starting/xs && isInTimeframe ($hash, $c) && $iilt) { + Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - switching on postponed by >isInLocktime<}); + } } - if ($auto && $oncom && $swoncond && !$swoffcond && !$iilt && # kein Einschalten wenn zusätzliche Switch off Bedingung zutrifft + if ($auto && $oncom && $swoncond && !$swoffcond && !$iilt && # kein Einschalten wenn zusätzliche Switch off Bedingung oder Sperrzeit zutrifft simplifyCstate($pstate) =~ /planned|priority|starting/xs && isInTimeframe ($hash, $c)) { # Verbraucher Start ist geplant && Startzeit überschritten my $mode = ConsumerVal ($hash, $c, "mode", $defcmode); # Consumer Planungsmode @@ -5929,8 +5989,8 @@ sub ___switchConsumerOn { delete $paref->{ps}; - my $caution = isInterruptable($hash, $c) == 3 ? 'interrupt condition no longer present' : 'existing surplus'; - $state = qq{switching Consumer '$calias' to '$oncom', caution: $caution}; + my $cause = isInterruptable($hash, $c) == 3 ? 'interrupt condition no longer present' : 'existing surplus'; + $state = qq{switching Consumer '$calias' to '$oncom', cause: $cause}; writeCacheToFile ($hash, "consumers", $csmcache.$name); # Cache File Consumer schreiben @@ -5965,10 +6025,16 @@ sub ___switchConsumerOff { Log3 ($name, 1, "$name - $err") if($err); + my ($iilt,$rlt) = isInLocktime ($paref); # Sperrzeit Status ermitteln + debugLog ($paref, "consumerSwitching", qq{consumer "$c" - current Context is switching "off" => }. qq{swoffcond: $swoffcond, off-command: $offcom}); + + if ($stopts && $t >= $stopts && $iilt) { + debugLog ($paref, "consumerSwitching", qq{consumer "$c" - switching off postponed by >isInLocktime<}); + } - if(($swoffcond || ($stopts && $t >= $stopts)) && + if(($swoffcond || ($stopts && $t >= $stopts)) && !$iilt && ($auto && $offcom && simplifyCstate($pstate) =~ /started|starting|stopping|interrupt|continu/xs)) { CommandSet(undef,"$cname $offcom"); @@ -5987,7 +6053,7 @@ sub ___switchConsumerOff { } elsif (((isInterruptable($hash, $c, $hyst) && !isConsRcmd ($hash, $c)) || isInterruptable($hash, $c, $hyst) == 2) && # Consumer unterbrechen isInTimeframe ($hash, $c) && simplifyCstate ($pstate) =~ /started|continued|interrupting/xs && - $auto && $offcom) { + $auto && $offcom && !$iilt) { CommandSet(undef,"$cname $offcom"); @@ -6027,6 +6093,8 @@ sub ___setConsumerSwitchingState { my $pstate = simplifyCstate (ConsumerVal ($hash, $c, "planstate", "")); my $calias = ConsumerVal ($hash, $c, "alias", ""); # Consumer Device Alias my $auto = ConsumerVal ($hash, $c, "auto", 1); + + debugLog ($paref, "consumerSwitching", qq{consumer "$c" - current planning state: $pstate \n}); if ($pstate eq 'starting' && isConsumerPhysOn ($hash, $c)) { my $mintime = ConsumerVal ($hash, $c, "mintime", $defmintime); @@ -6037,16 +6105,17 @@ sub ___setConsumerSwitchingState { $mintime /= 60; } - my $stopdiff = $mintime * 60; - - $paref->{ps} = "switched on:"; - $paref->{startts} = $t; - $paref->{stopts} = $t + $stopdiff; + my $stopdiff = $mintime * 60; + $paref->{ps} = "switched on:"; + $paref->{startts} = $t; + $paref->{lastAutoOnTs} = $t; + $paref->{stopts} = $t + $stopdiff; ___setConsumerPlanningState ($paref); delete $paref->{ps}; delete $paref->{startts}; + delete $paref->{lastAutoOnTs}; delete $paref->{stopts}; $state = qq{Consumer '$calias' switched on}; @@ -6074,10 +6143,12 @@ sub ___setConsumerSwitchingState { } elsif ($pstate eq 'continuing' && isConsumerPhysOn ($hash, $c)) { $paref->{ps} = "continued:"; + $paref->{lastAutoOnTs} = $t; ___setConsumerPlanningState ($paref); delete $paref->{ps}; + delete $paref->{lastAutoOnTs}; $state = qq{Consumer '$calias' switched on (continued)}; @@ -6366,8 +6437,6 @@ sub _estConsumptionForecast { my $totcon = 0; my $dnum = 0; my $consumerco = 0; - #my $min = (~0 >> 1); - #my $max = -(~0 >> 1); for my $n (sort{$a<=>$b} keys %{$data{$type}{$name}{pvhist}}) { next if ($n eq $day); # aktuellen (unvollständigen) Tag nicht berücksichtigen @@ -6380,22 +6449,11 @@ sub _estConsumptionForecast { my $dcon = HistoryVal ($hash, $n, 99, "con", 0); next if(!$dcon); - #for my $c (sort{$a<=>$b} keys %{$acref}) { # historischen Verbrauch aller registrierten Verbraucher aufaddieren - # $consumerco += HistoryVal ($hash, $n, 99, "csme${c}", 0); - #} - - #$dcon -= $consumerco if($dcon >= $consumerco); # Verbrauch registrierter Verbraucher aus Verbrauchsvorhersage eliminieren - - #$min = $dcon if($dcon < $min); - #$max = $dcon if($dcon > $max); - $totcon += $dcon; $dnum++; } if ($dnum) { - #my $ddiff = ($max - $min)/$dnum; # Glättungsdifferenz - #my $tomavg = int (($totcon/$dnum)-$ddiff); my $tomavg = int ($totcon / $dnum); $data{$type}{$name}{current}{tomorrowconsumption} = $tomavg; # prognostizierter Durchschnittsverbrauch aller (gleicher) Wochentage @@ -6430,8 +6488,6 @@ sub _estConsumptionForecast { $dnum = 0; $consumerco = 0; - #$min = (~0 >> 1); - #$max = -(~0 >> 1); my $utime = timestringToTimestamp ($nhtime); 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) @@ -6451,18 +6507,13 @@ sub _estConsumptionForecast { $consumerco += HistoryVal ($hash, $m, $nhhr, "csme${c}", 0); } - #$hcon -= $consumerco if($hcon >= $consumerco); # Verbrauch registrierter Verbraucher aus Verbrauch eliminieren $conhex->{$nhhr} += $hcon - $consumerco if($hcon >= $consumerco); # prognostizierter Verbrauch Ex registrierter Verbraucher - #$min = $hcon if($hcon < $min); - #$max = $hcon if($hcon > $max); $conh->{$nhhr} += $hcon; $dnum++; } if ($dnum) { - #my $hdiff = ($max - $min)/$dnum; # Glättungsdifferenz - #my $conavg = int(($conh->{$nhhr}/$dnum)-$hdiff); $data{$type}{$name}{nexthours}{$k}{confcEx} = int ($conhex->{$nhhr} / $dnum); my $conavg = int ($conh->{$nhhr} / $dnum); @@ -6987,9 +7038,24 @@ sub collectAllRegConsumers { Log3 ($name, 1, "$name - $err"); next; } - + push @{$data{$type}{$name}{current}{consumerdevs}}, $consumer; # alle Consumerdevices in CurrentHash eintragen + my $dswitch = $hc->{switchdev}; # alternatives Schaltdevice + + if ($dswitch) { + if(!$defs{$dswitch}) { + my $err = qq{ERROR - the device "$dswitch" doesn't exist anymore! Delete or change the attribute "consumer${c}".}; + Log3 ($name, 1, "$name - $err"); + next; + } + + push @{$data{$type}{$name}{current}{consumerdevs}}, $dswitch; # Switchdevice zusätzlich in CurrentHash eintragen + } + else { + $dswitch = $consumer; + } + my $alias = AttrVal ($consumer, "alias", $consumer); my ($rtot,$utot,$ethreshold); @@ -7008,7 +7074,7 @@ sub collectAllRegConsumers { if(exists $hc->{asynchron}) { $asynchron = $hc->{asynchron}; } - + my ($rswstate,$onreg,$offreg); if(exists $hc->{swstate}) { ($rswstate,$onreg,$offreg) = split ":", $hc->{swstate}; @@ -7070,6 +7136,7 @@ sub collectAllRegConsumers { $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 + $data{$type}{$name}{consumers}{$c}{dswitch} = $dswitch; # Switchdevice zur Kommandoausführung $data{$type}{$name}{consumers}{$c}{autoreading} = $rauto; # Readingname zur Automatiksteuerung $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 @@ -7082,7 +7149,7 @@ sub collectAllRegConsumers { $data{$type}{$name}{consumers}{$c}{notafter} = $hc->{notafter} // q{}; # nicht einschalten nach Stunde in 24h Format (00-23) $data{$type}{$name}{consumers}{$c}{rswstate} = $rswstate // 'state'; # Schaltstatus Reading $data{$type}{$name}{consumers}{$c}{asynchron} = $asynchron // 0; # Arbeitsweise FHEM Consumer Device - $data{$type}{$name}{consumers}{$c}{locktime} = $clt // 0; # Sperrzeit im Automatikmodus + $data{$type}{$name}{consumers}{$c}{locktime} = $clt // '0:0'; # Sperrzeit im Automatikmodus ('offlt:onlt') $data{$type}{$name}{consumers}{$c}{onreg} = $onreg // 'on'; # Regex für 'ein' $data{$type}{$name}{consumers}{$c}{offreg} = $offreg // 'off'; # Regex für 'aus' $data{$type}{$name}{consumers}{$c}{dswoncond} = $dswoncond // q{}; # Device zur Lieferung einer zusätzliche Einschaltbedingung @@ -7401,7 +7468,7 @@ sub _checkSetupNotComplete { my $link = qq{$name}; my $height = AttrNum ($name, 'graphicBeamHeight', 200); - my $lang = AttrVal ($name, 'ctrlLanguage', AttrVal ('global', 'language', $deflang)); + my $lang = getLang ($hash); if(IsDisabled($name)) { $ret .= "