76_SolarForecast: contrib Version 1.60.0
git-svn-id: https://svn.fhem.de/fhem/trunk@30469 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
@@ -40,17 +40,17 @@ use Time::HiRes qw(gettimeofday tv_interval);
|
|||||||
use Math::Trig;
|
use Math::Trig;
|
||||||
use List::Util qw(sum min max shuffle);
|
use List::Util qw(sum min max shuffle);
|
||||||
use Scalar::Util qw(blessed weaken);
|
use Scalar::Util qw(blessed weaken);
|
||||||
|
|
||||||
eval "use FHEM::Meta;1" or my $modMetaAbsent = 1; ## no critic 'eval'
|
|
||||||
eval "use FHEM::Utility::CTZ qw(:all);1;" or my $ctzAbsent = 1; ## no critic 'eval'
|
|
||||||
#use Test::Memory::Usage; # https://metacpan.org/pod/Test::Memory::Usage
|
|
||||||
|
|
||||||
use Encode;
|
use Encode;
|
||||||
use Color;
|
use Color;
|
||||||
use utf8;
|
use utf8;
|
||||||
use HttpUtils;
|
use HttpUtils;
|
||||||
|
|
||||||
|
eval "use FHEM::Meta;1" or my $modMetaAbsent = 1; ## no critic 'eval'
|
||||||
|
eval "use FHEM::Utility::CTZ qw(:all);1;" or my $ctzAbsent = 1; ## no critic 'eval'
|
||||||
|
#use Test::Memory::Usage; # https://metacpan.org/pod/Test::Memory::Usage
|
||||||
eval "use JSON;1;" or my $jsonabs = 'JSON'; ## no critic 'eval' # cpan install JSON
|
eval "use JSON;1;" or my $jsonabs = 'JSON'; ## no critic 'eval' # cpan install JSON
|
||||||
eval "use AI::DecisionTree;1;" or my $aidtabs = 'AI::DecisionTree'; ## no critic 'eval' # cpan install AI::DecisionTree
|
eval "use AI::DecisionTree;1;" or my $aidtabs = 'AI::DecisionTree'; ## no critic 'eval' # cpan install AI::DecisionTree
|
||||||
|
eval "use Digest::SHA qw(sha1_hex);1;" or my $digestAbsent = 'Digest::SHA'; ## no critic 'eval'
|
||||||
|
|
||||||
use FHEM::SynoModules::ErrCodes qw(:all); # Error Code Modul
|
use FHEM::SynoModules::ErrCodes qw(:all); # Error Code Modul
|
||||||
use FHEM::SynoModules::SMUtils qw (checkModVer
|
use FHEM::SynoModules::SMUtils qw (checkModVer
|
||||||
@@ -160,14 +160,16 @@ BEGIN {
|
|||||||
|
|
||||||
# Versions History intern
|
# Versions History intern
|
||||||
my %vNotesIntern = (
|
my %vNotesIntern = (
|
||||||
"1.60.0" => "30.10.2025 ___ownSpecGetFWwidget: handling of line breaks in attributes & can hamdle a key=value pair separateley ".
|
"1.60.0" => "01.11.2025 ___ownSpecGetFWwidget: handling of line breaks in attributes & can hamdle a key=value pair separateley ".
|
||||||
"Width of a text field in graphicHeaderOwnspec fixed to 10, edit commandref ".
|
"Width of a text field in graphicHeaderOwnspec fixed to 10, edit commandref ".
|
||||||
"__batChargeOptTargetPower: use an average for the charging power if smartPower set and charging target are not achievable ".
|
"__batChargeOptTargetPower: use an average for the charging power if smartPower set and charging target are not achievable ".
|
||||||
"__createOwnSpec: an empty field can be created within a line by simply using a colon (:). ".
|
"__createOwnSpec: an empty field can be created within a line by simply using a colon (:). ".
|
||||||
"add new key pvshare to CustomerXX attributes -> __setConsRcmdState add PV share calculation ".
|
"add new key pvshare to CustomerXX attributes -> __setConsRcmdState add PV share calculation ".
|
||||||
"___doPlanning: code improvements and implement PV share needed ".
|
"___doPlanning: code improvements and implement PV share needed ".
|
||||||
" Task 2: chamge timestamp of day before to 24:00:00, _restorePlantConfig: fix problem with attr sequence ".
|
" Task 2: chamge timestamp of day before to 24:00:00, _restorePlantConfig: fix problem with attr sequence ".
|
||||||
"_setreset: set reset is reworked with widgetList, aiData can be deleted by index ",
|
"_setreset: set reset is reworked with widgetList, aiData can be deleted by index ".
|
||||||
|
"_flowGraphic: new variable node2home_direction ".
|
||||||
|
"new sub askLogtime to avoid error logs too often, Forum: https://forum.fhem.de/index.php?msg=1350716 ",
|
||||||
"1.59.5" => "15.10.2025 new sub ___batAdjustPowerByMargin: implement optPower Safety margin decreasing proportionally to the linear surplus ".
|
"1.59.5" => "15.10.2025 new sub ___batAdjustPowerByMargin: implement optPower Safety margin decreasing proportionally to the linear surplus ".
|
||||||
"new Reading Battery_TargetAchievable_XX, _batSocTarget: minor code change ",
|
"new Reading Battery_TargetAchievable_XX, _batSocTarget: minor code change ",
|
||||||
"1.59.4" => "14.10.2025 new subs, ctrlBatSocManagementXX: new key loadTarget, replace __batCapShareFactor by __batDeficitShareFactor ".
|
"1.59.4" => "14.10.2025 new subs, ctrlBatSocManagementXX: new key loadTarget, replace __batCapShareFactor by __batDeficitShareFactor ".
|
||||||
@@ -1650,6 +1652,7 @@ my %hfspvh = (
|
|||||||
# $data{$name}{batteries} # temporärer Speicher Battery Daten
|
# $data{$name}{batteries} # temporärer Speicher Battery Daten
|
||||||
# $data{$name}{weatherdata} # temporärer Speicher Wetterdaten
|
# $data{$name}{weatherdata} # temporärer Speicher Wetterdaten
|
||||||
# $data{$name}{func} # temporäre interne Funktionen
|
# $data{$name}{func} # temporäre interne Funktionen
|
||||||
|
# $data{$name}{log} # Logsperrhash
|
||||||
# $data{$name}{dwdcatalog} # temporärer Speicher DWD Stationskatalog
|
# $data{$name}{dwdcatalog} # temporärer Speicher DWD Stationskatalog
|
||||||
# $data{$name}{strings} # temporärer Speicher Stringkonfiguration
|
# $data{$name}{strings} # temporärer Speicher Stringkonfiguration
|
||||||
# $data{$name}{aidectree}{object} # AI Decision Tree Object (im BlockingCall)
|
# $data{$name}{aidectree}{object} # AI Decision Tree Object (im BlockingCall)
|
||||||
@@ -9683,6 +9686,13 @@ sub _specialActivities {
|
|||||||
__createAdditionalEvents ($paref); # zusätzliche Events erzeugen - PV Vorhersage bis Ende des kommenden Tages
|
__createAdditionalEvents ($paref); # zusätzliche Events erzeugen - PV Vorhersage bis Ende des kommenden Tages
|
||||||
__delObsoleteAPIData ($paref); # Bereinigung obsoleter Daten im solcastapi Hash
|
__delObsoleteAPIData ($paref); # Bereinigung obsoleter Daten im solcastapi Hash
|
||||||
|
|
||||||
|
my $ttl = 24 * 3600; # Logsperrhash: Lebenszeit eines Eintrags bevor er entfernt wird
|
||||||
|
my $cutoff = $t - $ttl;
|
||||||
|
|
||||||
|
for my $sh1 (keys %{ $data{$name}{log} }) { # Logsperrhash bereinigen
|
||||||
|
delete $data{$name}{log}{$sh1} if($data{$name}{log}{$sh1}{ts} // 0 < $cutoff);
|
||||||
|
}
|
||||||
|
|
||||||
Log3 ($name, 4, "$name - Daily special tasks - Task 4 finished");
|
Log3 ($name, 4, "$name - Daily special tasks - Task 4 finished");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12437,7 +12447,7 @@ sub ___batAdjustPowerByMargin {
|
|||||||
return ($limpower * (1 + $otpMargin / 100), $ratio) if($limpower == 0 || !$otpMargin || $ratio >= 100 + $otpMargin);
|
return ($limpower * (1 + $otpMargin / 100), $ratio) if($limpower == 0 || !$otpMargin || $ratio >= 100 + $otpMargin);
|
||||||
|
|
||||||
if ($ratio <= 100) {
|
if ($ratio <= 100) {
|
||||||
$pow = $ratio <= 50 ? $pinmax : $limpower * (1 + $otpMargin / 100);
|
$pow = $pinmax;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$pow = $pinmax - ($pinmax - $limpower) * ($ratio - 100) / $otpMargin;
|
$pow = $pinmax - ($pinmax - $limpower) * ($ratio - 100) / $otpMargin;
|
||||||
@@ -14008,7 +14018,7 @@ sub ___switchConsumerOn {
|
|||||||
|
|
||||||
if ($err) {
|
if ($err) {
|
||||||
$state = 'ERROR - '.$err;
|
$state = 'ERROR - '.$err;
|
||||||
Log3 ($name, 1, "$name - $state");
|
Log3 ($name, 1, "$name - $state") if(askLogtime ($name, $err));
|
||||||
return $state;
|
return $state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19188,11 +19198,12 @@ END1
|
|||||||
## Laufketten Node->Home, Node->Grid, Bat->Home
|
## Laufketten Node->Home, Node->Grid, Bat->Home
|
||||||
#################################################
|
#################################################
|
||||||
my $node2home_style = $node2home ? "$stna active_normal" : "$stna inactive";
|
my $node2home_style = $node2home ? "$stna active_normal" : "$stna inactive";
|
||||||
|
my $node2home_direction = $node2home < 0 ? "M700,580 L700,400" : "M700,400 L700,580";
|
||||||
my $node2gridMetered_style = $node2gridMetered ? "$stna active_normal" : "$stna inactive";
|
my $node2gridMetered_style = $node2gridMetered ? "$stna active_normal" : "$stna inactive";
|
||||||
|
|
||||||
$ret .= << "END2";
|
$ret .= << "END2";
|
||||||
<g transform="translate(50,50),scale(0.5)" stroke-width="27" fill="none">
|
<g transform="translate(50,50),scale(0.5)" stroke-width="27" fill="none">
|
||||||
<path id="node2home_$stna" class="$node2home_style" d="M700,400 L700,580" />
|
<path id="node2home_$stna" class="$node2home_style" d="$node2home_direction" />
|
||||||
<path id="node2grid_$stna" class="$node2gridMetered_style" d="M670,400 L250,480" />
|
<path id="node2grid_$stna" class="$node2gridMetered_style" d="M670,400 L250,480" />
|
||||||
<path id="grid2home_$stna" class="$grid2home_style" d="$gconMetered_direction" />
|
<path id="grid2home_$stna" class="$grid2home_style" d="$gconMetered_direction" />
|
||||||
END2
|
END2
|
||||||
@@ -23109,7 +23120,7 @@ sub determSurplus {
|
|||||||
|
|
||||||
if ($err) {
|
if ($err) {
|
||||||
$fallback = 1;
|
$fallback = 1;
|
||||||
Log3 ($name, 1, qq{$name - ERROR of consumer $c key 'surpmeth': $err (fall back to default Surplus determination)});
|
Log3 ($name, 1, qq{$name - ERROR of consumer $c key 'surpmeth': $err (fall back to default Surplus determination)}) if(askLogtime ($name, $err));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$surplus = ReadingsNum ($dv, $rd, '');
|
$surplus = ReadingsNum ($dv, $rd, '');
|
||||||
@@ -23517,6 +23528,47 @@ sub debugLog {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
################################################################
|
||||||
|
# Ausgabe einer Log-Message nach Zeit erlauben.
|
||||||
|
# Gibt "wahr" zurück wenn die Message noch nicht oder vor
|
||||||
|
# längerer Zeit als $delay Sekunden ausgegeben wurde
|
||||||
|
#
|
||||||
|
# delay => Sek. bis gleiche Meldung wieder geloggt werden darf
|
||||||
|
# (default 600)
|
||||||
|
################################################################
|
||||||
|
sub askLogtime {
|
||||||
|
my $name = shift;
|
||||||
|
my $err = shift;
|
||||||
|
my $delay = shift // 600;
|
||||||
|
|
||||||
|
return if(!$err);
|
||||||
|
|
||||||
|
my $dolog = 1;
|
||||||
|
|
||||||
|
if ($digestAbsent) {
|
||||||
|
Log3 ($name, 1, "$name - ERROR - The Perl module $digestAbsent is missing. Please install it");
|
||||||
|
return $dolog;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $now = time;
|
||||||
|
$data{$name}{log} = {} unless ref $data{$name}{log} eq 'HASH'; # sicherstellen, dass Struktur existiert
|
||||||
|
my $sha1 = sha1_hex ($err);
|
||||||
|
|
||||||
|
if (my $entry = $data{$name}{log}{$sha1}) {
|
||||||
|
$entry->{msg} = $err unless defined $entry->{msg} && $entry->{msg} eq $err; # falls der gespeicherte Text aus irgendeinem Grund anders ist, aktualisiere ihn
|
||||||
|
|
||||||
|
if ($entry->{ts} && $entry->{ts} + $delay > $now) {
|
||||||
|
$dolog = 0; # noch innerhalb der Drosselzeit -> nicht loggen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dolog) {
|
||||||
|
$data{$name}{log}{$sha1} = { ts => $now, msg => $err }; # Eintrag aktualisieren / anlegen wenn log erlaubt ist
|
||||||
|
}
|
||||||
|
|
||||||
|
return $dolog;
|
||||||
|
}
|
||||||
|
|
||||||
##################################################################
|
##################################################################
|
||||||
# Konvertiert Azimut von der Solar-Konvention (+180 .. 0 .. -180)
|
# Konvertiert Azimut von der Solar-Konvention (+180 .. 0 .. -180)
|
||||||
# in die astronomische Konvention (0 ... 360°)
|
# in die astronomische Konvention (0 ... 360°)
|
||||||
@@ -23734,7 +23786,7 @@ sub getConsumerPlanningMode {
|
|||||||
my ($err) = isDeviceValid ( { name => $hash->{NAME}, obj => $dv, method => 'string' } );
|
my ($err) = isDeviceValid ( { name => $hash->{NAME}, obj => $dv, method => 'string' } );
|
||||||
|
|
||||||
if ($err) {
|
if ($err) {
|
||||||
Log3 ($name, 1, qq{$name - ERROR - consumer >$c< - The device '$dv' in consumer key 'mode' doesn't exist. Fall back to 'DEFCMODE' mode.});
|
Log3 ($name, 1, "$name - ERROR - consumer >$c< - The device '$dv' in consumer key 'mode' doesn't exist. Fall back to ".DEFCMODE." mode.") if(askLogtime ($name, $err));
|
||||||
return DEFCMODE;
|
return DEFCMODE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23943,7 +23995,7 @@ sub isConsumerPhysOn {
|
|||||||
my ($err, $cname, $dswname) = getCDnames ($hash, $c); # Consumer und Switch Device Name
|
my ($err, $cname, $dswname) = getCDnames ($hash, $c); # Consumer und Switch Device Name
|
||||||
|
|
||||||
if ($err) {
|
if ($err) {
|
||||||
Log3 ($name, 1, "$name - ERROR - $err");
|
Log3 ($name, 1, "$name - ERROR - $err") if(askLogtime ($name, $err));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23970,7 +24022,7 @@ sub isConsumerPhysOff {
|
|||||||
my ($err, $cname, $dswname) = getCDnames ($hash, $c); # Consumer und Switch Device Name
|
my ($err, $cname, $dswname) = getCDnames ($hash, $c); # Consumer und Switch Device Name
|
||||||
|
|
||||||
if ($err) {
|
if ($err) {
|
||||||
Log3 ($name, 1, "$name - ERROR - $err");
|
Log3 ($name, 1, "$name - ERROR - $err") if(askLogtime ($name, $err));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24003,7 +24055,7 @@ sub isConsumerLogOn {
|
|||||||
my ($err) = isDeviceValid ( { name => $name, obj => $cname, method => 'string' } );
|
my ($err) = isDeviceValid ( { name => $name, obj => $cname, method => 'string' } );
|
||||||
|
|
||||||
if ($err) {
|
if ($err) {
|
||||||
Log3 ($name, 1, qq{$name - ERROR - The consumer device '$cname' is invalid. The 'on'-state can't be identified.});
|
Log3 ($name, 1, qq{$name - ERROR - The consumer device '$cname' is invalid. The 'on'-state can't be identified.}) if(askLogtime ($name, $err));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24664,13 +24716,13 @@ sub isDeviceValid {
|
|||||||
|
|
||||||
if (!$dv || !$defs{$dv}) {
|
if (!$dv || !$defs{$dv}) {
|
||||||
$dv //= '';
|
$dv //= '';
|
||||||
$err = qq{The device '$dv' doesn't exist or is not a valid device.};
|
$err = qq{The device '$dv' doesn't exist or is not a valid device};
|
||||||
$err = qq{There is no device set. Check the syntax with the command reference.} if(!$dv);
|
$err = qq{There is no device set. Check the syntax with the command reference} if(!$dv);
|
||||||
$err = qq{The device '$dv' doesn't exist anymore! Delete or change the attribute '$obj'.} if(!$defs{$dv} && $method eq 'attr' && $obj =~ /consumer/);
|
$err = qq{The device '$dv' doesn't exist anymore! Delete or change the attribute '$obj'} if(!$defs{$dv} && $method eq 'attr' && $obj =~ /consumer/);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($err) {
|
if ($err) {
|
||||||
Log3 ($name, 1, "$name - ERROR - $err");
|
Log3 ($name, 1, "$name - ERROR - $err") if(askLogtime ($name, $err));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($al) { # Leerzeichen im SF-Alias generieren
|
if ($al) { # Leerzeichen im SF-Alias generieren
|
||||||
@@ -24886,7 +24938,8 @@ sub lastConsumerSwitchtime {
|
|||||||
my ($err, $cname, $dswname) = getCDnames ($hash, $c); # Consumer und Switch Device Name
|
my ($err, $cname, $dswname) = getCDnames ($hash, $c); # Consumer und Switch Device Name
|
||||||
|
|
||||||
if ($err) {
|
if ($err) {
|
||||||
Log3 ($name, 1, qq{$name - ERROR - The last switching time can't be identified due to the device '$dswname' is invalid. Please check device names in consumer "$c" attribute});
|
Log3 ($name, 1, qq{$name - ERROR - The last switching time can't be identified due to the device '$dswname' is invalid.
|
||||||
|
Please check device names in consumer "$c" attribute}) if(askLogtime ($name, $err));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31673,6 +31726,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
|
|||||||
"Blocking": 0,
|
"Blocking": 0,
|
||||||
"Color": 0,
|
"Color": 0,
|
||||||
"utf8": 0,
|
"utf8": 0,
|
||||||
|
"Digest::SHA": 0,
|
||||||
"HttpUtils": 0,
|
"HttpUtils": 0,
|
||||||
"JSON": 4.020,
|
"JSON": 4.020,
|
||||||
"FHEM::SynoModules::SMUtils": 1.0270,
|
"FHEM::SynoModules::SMUtils": 1.0270,
|
||||||
|
|||||||
Reference in New Issue
Block a user