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:
DS_Starter
2025-11-01 13:27:19 +00:00
parent c49eb31d38
commit f77aa6f262

View File

@@ -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,