2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-01-31 00:36:25 +00:00

76_SolarForecast: consumer device may have specified an own alias

git-svn-id: https://svn.fhem.de/fhem/trunk@29559 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
nasseeder1 2025-01-22 22:32:23 +00:00
parent d04d74bb5a
commit 33735a1418
2 changed files with 474 additions and 220 deletions

View File

@ -1,5 +1,6 @@
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
# Do not insert empty lines here, update check depends on it
- feature: 76_SolarForecast: consumer device may have specified an own alias
- feature: 73_AutoShuttersControl:
https://forum.fhem.de/index.php?topic=136510.0
- feature: 76_SolarForecast: add temporary Migrate Getter x_migrate,

View File

@ -104,6 +104,8 @@ BEGIN {
getAllSets
HttpUtils_NonblockingGet
HttpUtils_BlockingGet
GetFileFromURL
GetHttpFile
init_done
InternalTimer
InternalVal
@ -157,6 +159,8 @@ BEGIN {
# Versions History intern
my %vNotesIntern = (
"1.44.1" => "20.01.2025 Notification system: minor fixes, integration of controls_solarforecast_messages_test/prod ".
"Define: random start of Timer subs, consumerXX: consumer device may have specified an own alias ",
"1.44.0" => "19.01.2025 _listDataPoolCircular: may select a dedicated hour, add temporary Migrate funktion x_migrate ".
"fix interruptable key check in consumer attr Forum:https://forum.fhem.de/index.php?msg=1331073 ".
"set prdef to 1.0, Implementation of a Messaging System ",
@ -423,6 +427,7 @@ my $calcmaxd = 30;
my @dweattrmust = qw(TTT Neff RR1c 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 = 851; # Wiederholungsintervall Cache File Daten schreiben
my $gmfblto = 30; # Timeout Aholen Message File aus contrib
my $solapirepdef = 3600; # default Abrufintervall SolCast API (s)
my $forapirepdef = 900; # default Abrufintervall ForecastSolar API (s)
@ -489,10 +494,14 @@ my $actcoldef = 'orange';
my $inactcoldef = 'grey'; # default Färbung Icon wenn inaktiv
my $bPath = 'https://svn.fhem.de/trac/browser/trunk/fhem/contrib/SolarForecast/'; # Basispfad Abruf contrib SolarForecast Files
my $cfile = 'controls_solarforecast.txt'; # Controlfile Update FTUI-Files
my $msgfiletest = 'controls_solarforecast_messages_test.txt'; # TEST Input-File Notification System
my $msgfileprod = 'controls_solarforecast_messages_prod.txt'; # PRODUKTIVES Input-File Notification System
my $pPath = '?format=txt'; # Download Format
my $cfile = 'controls_solarforecast.txt'; # Name des Controlfiles
my $gmfilerepeat = 3000; # Wiederholungsuntervall Aholen Message File aus contrib
my $idxlimit = 900000; # Notification System: Indexe > $idxlimit sind reserviert für Steuerungsaufgaben
my $messagefile = $msgfileprod;
# mögliche Debug-Module
my @dd = qw( aiProcess
aiData
@ -803,12 +812,14 @@ my %hqtxt = ( # H
DE => qq{heute} },
simsg => { EN => qq{Message},
DE => qq{Mitteilung} },
msgsys => { EN => qq{Messaging system},
msgsys => { EN => qq{Notification system},
DE => qq{Mitteilungssystem} },
msgimp => { EN => qq{Importance},
DE => qq{Wichtigkeit} },
number => { EN => qq{Number},
DE => qq{Nummer} },
ludich => { EN => qq{last update Input channels},
DE => qq{letzte Aktualisierung Eingangskanäle} },
ctnsly => { EN => qq{continuously},
DE => qq{fortlaufend} },
yday => { EN => qq{yesterday},
@ -871,6 +882,8 @@ my %hqtxt = ( # H
DE => qq{Oh nein 😢, die Anlagenkonfiguration ist fehlerhaft. Bitte überprüfen Sie die Einstellungen und Hinweise!} },
pstate => { EN => qq{Planning&nbsp;status:&nbsp;<pstate><br>Info:&nbsp;<supplmnt><br>Mode:&nbsp;<mode><br>On:&nbsp;<start><br>Off:&nbsp;<stop><br>Remaining lock time:&nbsp;<RLT> seconds},
DE => qq{Planungsstatus:&nbsp;<pstate><br>Info:&nbsp;<supplmnt><br>Modus:&nbsp;<mode><br>Ein:&nbsp;<start><br>Aus:&nbsp;<stop><br>verbleibende Sperrzeit:&nbsp;<RLT> Sekunden} },
dmgsig => { EN => qq{Read messages are not signaled again until a FHEM restart, but are retained if they are relevant.},
DE => qq{Gelesene Mitteilungen werden bis zu einem FHEM Neustart nicht wieder signalisiert, bleiben bei Relevanz jedoch erhalten.} },
);
my %htitles = ( # Hash Hilfetexte (Mouse Over)
@ -1301,7 +1314,9 @@ my %hfspvh = (
# $data{$name}{dwdcatalog} # temporärer Speicher DWD Stationskatalog
# $data{$name}{strings} # temporärer Speicher Stringkonfiguration
# $data{$name}{aidectree}{object} # AI Decision Tree Object
# $data{$name}{messages} # Speicher Mitteilungssystem
# $data{$name}{messages} # Mitteilungssystem - permanent erneuerter Speicher
# $data{$name}{filemessages} # Mitteilungssystem - Input vom Message File
# $data{$name}{preparedmessages} # Mitteilungssystem - vorbereitete Messages innerhalb des Code
################################################################
# Init Fn
@ -1480,7 +1495,8 @@ sub Define {
singleUpdateState ( {hash => $hash, state => 'initialized', evt => 1} );
$readyfnlist{$name} = $hash; # Registrierung in Ready-Schleife
InternalTimer (gettimeofday()+$whistrepeat, "FHEM::SolarForecast::periodicWriteMemcache", $hash, 0); # Einstieg periodisches Schreiben historische Daten
InternalTimer (gettimeofday() + $whistrepeat + int(rand(300)), "FHEM::SolarForecast::periodicWriteMemcache", $hash, 0); # Einstieg periodisches Schreiben historische Daten
InternalTimer (gettimeofday() + 120 + int(rand(300)), "FHEM::SolarForecast::getMessageFileNonBlocking", $hash, 0);
return;
}
@ -4661,7 +4677,7 @@ sub _getoutputMessages { ## no critic "not used"
my $out = outputMessages ($paref);
$out = qq{<html>$out</html>};
$data{$name}{messages}{9999}{RD} = 1; # Lesekennzeichen setzen
$data{$name}{messages}{999999}{RD} = 1; # Lesekennzeichen setzen
## asynchrone Ausgabe
#######################
@ -5400,21 +5416,10 @@ sub __updPreFile {
$err .= "Please check whether the path $path is present and accessible.<br>";
$err .= "After installing FTUI, come back and execute the get command again.";
return $err;
#my $ok = mkdir $path;
#if (!$ok) {
# $err = "MKDIR ERROR: $!";
# Log3 ($name, 1, "$name - $err");
# return $err;
#}
#else {
# Log3 ($name, 3, "$name - MKDIR $path");
#}
}
}
($err, my $remFile) = __updGetUrl ($name, $bPath.$file.$pPath);
($err, my $remFile) = __httpBlockingGet ($name, $bPath.$file.$pPath);
if ($err) {
Log3 ($name, 1, "$name - $err");
@ -5446,27 +5451,27 @@ return;
###############################################################
# File von url holen
###############################################################
sub __updGetUrl {
sub __httpBlockingGet {
my $name = shift;
my $url = shift;
$url =~ s/%/%25/g;
my %upd_connecthash;
my %connecthash;
my $unicodeEncoding = 1;
$upd_connecthash{url} = $url;
$upd_connecthash{keepalive} = ($url =~ m/localUpdate/ ? 0 : 1); # Forum #49798
$upd_connecthash{forceEncoding} = '' if($unicodeEncoding);
$connecthash{url} = $url;
$connecthash{keepalive} = ($url =~ m/localUpdate/ ? 0 : 1); # Forum #49798
$connecthash{forceEncoding} = '' if($unicodeEncoding);
my ($err, $dat) = HttpUtils_BlockingGet (\%upd_connecthash);
my ($err, $dat) = HttpUtils_BlockingGet (\%connecthash);
if ($err) {
$err = "update ERROR: $err";
$err = "GetUrl ERROR: $err";
return ($err, '');
}
if (!$dat) {
$err = 'update ERROR: empty file received';
$err = 'WARNING - empty file received';
return ($err, '');
}
@ -7638,6 +7643,7 @@ sub centralTask {
setTimeTracking ($hash, $cst, 'runTimeCentralTask'); # Zyklus-Laufzeit ermitteln
createReadingsFromArray ($hash, $evt); # Readings erzeugen
_readSystemMessages ($centpars); # Notification System - System Messages zusammenstellen
if ($evt) {
$centpars->{evt} = $evt;
@ -7822,7 +7828,7 @@ sub _collectAllRegConsumers {
for my $c (1..$maxconsumer) {
$c = sprintf "%02d", $c;
my ($err, $consumer, $hc) = isDeviceValid ( { name => $name, obj => "consumer${c}", method => 'attr' } );
my ($err, $consumer, $hc, $alias) = isDeviceValid ( { name => $name, obj => "consumer${c}", method => 'attr' } );
next if($err);
push @{$data{$name}{current}{consumerdevs}}, $consumer; # alle Consumerdevices in CurrentHash eintragen
@ -7839,7 +7845,7 @@ sub _collectAllRegConsumers {
$dswitch = $consumer;
}
my $alias = AttrVal ($consumer, 'alias', $consumer);
$alias = AttrVal ($consumer, 'alias', $consumer) if(!$alias);
my ($rtot,$utot,$ethreshold);
if (exists $hc->{etotal}) {
@ -9912,7 +9918,7 @@ return $sf;
}
################################################################
# Erstellung Batterie Ladefreigabe
# Erstellung Batterie Ladefreigabe + SoC Prognose
################################################################
sub _batChargeRecmd {
my $paref = shift;
@ -10047,10 +10053,6 @@ sub _batChargeRecmd {
$socwh = sprintf "%.0f", $socwh;
my $progsoc = sprintf "%.1f", (100 * $socwh / $batinstcap); # Prognose SoC in %
#$progsoc = $progsoc < $batoptsoc ? $batoptsoc :
# $progsoc < $lowSoc ? $lowSoc :
# $progsoc;
__createNextHoursSFCReadings ( {name => $name,
nhr => $nhr,
bn => $bn,
@ -12626,65 +12628,6 @@ sub __calcFcQuality {
return $hdv;
}
################################################################
# Berechnen 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);
}
################################################################
# Zeit gemäß DWD_OpenData-Format
# Berechnen Tag / Stunden Verschieber ab aktuellen Tag
# Input: YYYY-MM-DD HH:MM:SS
# Output: $fd - 0 (Heute), 1 (Morgen), 2 (Übermorgen), ....
# $fh - Stunde von $fd ohne führende Null
# Return: fc${fd}_${fh}
################################################################
sub formatWeatherTimestrg {
my $date = shift // return;
my $cdate = strftime "%Y-%m-%d", localtime(time);
my $refts = timestringToTimestamp ($cdate.' 00:00:00'); # Referenztimestring
my $datts = timestringToTimestamp ($date);
my $fd = int (($datts - $refts) / 86400);
my $fh = int ((split /[ :]/, $date)[1]);
return "fc${fd}_${fh}";
}
################################################################
# 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);
}
################################################################
# Energieverbrauch des Hauses in History speichern
################################################################
@ -12970,6 +12913,36 @@ sub _genSpecialReadings {
return;
}
###################################################################################
# Messagefile für Notification System lesen
# Filestruktur:
# 0|SV|1
# 0|DE|Mitteilung ....
# 0|EN|Message...
# $data{$name}{preparedmessages}{999500}{TS}: Timestamp Stand prepared Messages
###################################################################################
sub _readSystemMessages {
my $paref = shift;
my $name = $paref->{name};
delete $data{$name}{preparedmessages};
my $midx = 0;
if (!ReadingsVal ($name, '.migrated', 0)) {
$midx++;
$data{$name}{preparedmessages}{$midx}{SV} = 1;
$data{$name}{preparedmessages}{$midx}{DE} = 'Die gespeicherten PV Daten können mit "get ... x_migrate" in ein neues Format umgesetzt werden welches den Median Ansatz bei der PV Prognose aktiviert und nutzt.';
$data{$name}{preparedmessages}{$midx}{DE} .= '<br>Mit einem späteren Update des Moduls erfolgt diese Umstellung automatisch.';
$data{$name}{preparedmessages}{$midx}{EN} = 'The stored PV data can be converted with “get ... x_migrate” into a new format which activates and uses the median approach in the PV forecast.';
$data{$name}{preparedmessages}{$midx}{EN} .= '<br>With a later update of the module, this changeover will take place automatically.';
}
$data{$name}{preparedmessages}{999500}{TS} = time;
return;
}
################################################################
# FHEMWEB Fn
################################################################
@ -13483,7 +13456,7 @@ sub _graphicHeader {
## Message-Icon
#################
my ($micon, $midx) = __fillupMessages ($paref);
my ($micon, $midx) = fillupMessageSystem ($paref);
$img = FW_makeImage ($micon);
my $msgicon = $midx ? "<a onClick=$cmdoutmsg>$img</a>" : $img;
my $msgtitle = $midx ? $htitles{outpmsg}{$lang} : $htitles{nomsgfo}{$lang};
@ -13766,47 +13739,6 @@ sub _graphicHeader {
return $header;
}
################################################################
# Mitteilungssystem füllen
# Schweregrad SV:
# 0 - keine Mitteilung
# 1 - Mitteilung
# 2 - Warnung
# 3 - Fehler / Problem
################################################################
sub __fillupMessages {
my $paref = shift;
my $name = $paref->{name};
my $lang = $paref->{lang};
for my $idx (keys %{$data{$name}{messages}}) {
next if($idx == 9999);
delete $data{$name}{messages}{$idx};
}
my $midx = 0;
my $max_sv = 0;
if (!ReadingsVal ($name, '.migrated', 0)) {
$midx++;
$data{$name}{messages}{$midx}{SV} = 1;
$data{$name}{messages}{$midx}{DE} = 'Die gespeicherten PV Daten können mit "get ... x_migrate" in ein neues Format umgesetzt werden welches den Median Ansatz bei der PV Prognose aktiviert und nutzt.';
$data{$name}{messages}{$midx}{DE} .= '<br>Mit einem späteren Update des Moduls erfolgt diese Umstellung automatisch.';
$data{$name}{messages}{$midx}{EN} = 'The stored PV data can be converted with “get ... x_migrate” into a new format which activates and uses the median approach in the PV forecast.';
$data{$name}{messages}{$midx}{EN} .= '<br>With a later update of the module, this changeover will take place automatically.';
}
if ($midx && !defined $data{$name}{messages}{9999}{RD}) { # RD = Read-Bit
my @aidx = map { $_ } (1..$midx); # größte vorhandene Severity finden
my @values = map { $data{$name}{messages}{$_}{SV} } @aidx;
$max_sv = max(@values);
}
my $max_icon = $svicons{$max_sv}; # ... und das dazugehörige Icon
return ($max_icon, $midx);
}
################################################################
# erstelle Update-Icon
################################################################
@ -16501,6 +16433,313 @@ sub _addHourAiRawdata {
return;
}
###############################################################
# Abruf und Einlesen Messagefile nonBlocking
###############################################################
sub getMessageFileNonBlocking {
my $hash = shift;
my $name = $hash->{NAME};
RemoveInternalTimer ($hash, "FHEM::SolarForecast::getMessageFileNonBlocking");
InternalTimer (gettimeofday() + $gmfilerepeat, "FHEM::SolarForecast::getMessageFileNonBlocking", $hash, 0);
my (undef, $disabled, $inactive) = controller ($name);
return if($disabled || $inactive);
delete $hash->{HELPER}{GMFRUNNING} if(defined $hash->{HELPER}{GMFRUNNING}{pid} && $hash->{HELPER}{GMFRUNNING}{pid} =~ /DEAD/xs);
if (defined $hash->{HELPER}{GMFRUNNING}{pid}) {
Log3 ($name, 3, qq{$name - another Message File Process with PID "$hash->{HELPER}{GMFRUNNING}{pid}" is already running ... get Message File is aborted});
return;
}
Log3 ($name, 4, "$name - Notification System - Message file >$messagefile< is retrieved non blocking");
my $paref = { name => $name,
hash => $hash,
block => 1
};
$hash->{HELPER}{GMFRUNNING} = BlockingCall ( "FHEM::SolarForecast::_retrieveMessageFile",
$paref,
"FHEM::SolarForecast::_processMessageFile",
$gmfblto,
"FHEM::SolarForecast::_abortGetMessageFile",
$hash
);
if (defined $hash->{HELPER}{GMFRUNNING}) {
$hash->{HELPER}{GMFRUNNING}{loglevel} = 3;
}
return;
}
###############################################################
# Message File aus contrib abholen
###############################################################
sub _retrieveMessageFile {
my $paref = shift;
my $name = $paref->{name};
my $block = $paref->{block} // 0;
my $valid = 1;
my ($err, $remfile) = __httpBlockingGet ($name, $bPath.$messagefile.$pPath);
$remfile = q{} if($remfile =~ /No\snode\strunk\/fhem\/contrib\/SolarForecast\//xs);
if ($err) {
$valid = 0;
Log3 ($name, 4, "$name - Notification System - retrieve of remote Message File faulty: $err");
}
if (!$remfile) {
$valid = 0;
Log3 ($name, 4, "$name - Notification System - no remote Message File >$messagefile< found");
}
if ($valid) {
$err = __updWriteFile ("$root/FHEM/", $messagefile, $remfile);
if ($err) {
$valid = 0;
Log3 ($name, 1, "$name - $err");
}
else {
Log3 ($name, 4, "$name - Notification System - new Message File updated to $root/FHEM/$messagefile");
}
}
$paref->{valid} = $valid;
my $serial = encode_base64 (Serialize ( $paref ), ""); # Serialisierung
$block ? return ($serial) : return \&_processMessageFile ($serial);
return;
}
###############################################################
# Folgeroutine nach Message File aus contrib abholen
###############################################################
sub _processMessageFile {
my $serial = decode_base64 (shift);
my $paref = eval { thaw ($serial) }; # Deserialisierung
my $name = $paref->{name};
my $valid = $paref->{valid};
my $hash = $defs{$name};
if ($valid) {
__readFileMessages ($paref);
}
return;
}
######################################################################
# Messagefile für Notification System lesen
# Filestruktur:
# 0|SV|1
# 0|DE|Mitteilung ....
# 0|EN|Message...
# $data{$name}{messages}{999000}{TS}: Timestamp Stand Message File
######################################################################
sub __readFileMessages {
my $paref = shift;
my $name = $paref->{name};
my $hash = $defs{$name};
open (FD, "$root/FHEM/$messagefile") or do { return $! };
delete $data{$name}{filemessages};
my @locList = map { $_ =~ s/[\r\n]//; $_ } <FD>;
close (FD);
Log3 ($name, 4, "$name - Notification System - read local Message File >$messagefile< with ".scalar @locList." entries.");
for my $l (@locList) {
next if ($l =~ /^\#/xs);
my @l = split /\|/, $l, 3;
next if(!isNumeric ($l[0]));
next if($l[1] !~ /^(DE|EN|SV)$/xs);
$data{$name}{filemessages}{$l[0]}{$l[1]} = $l[2];
}
$data{$name}{filemessages}{999000}{TS} = time;
return;
}
####################################################################################################
# Abbruchroutine BlockingCall Timeout
####################################################################################################
sub _abortGetMessageFile {
my $hash = shift;
my $cause = shift // "Timeout: process terminated";
my $name = $hash->{NAME};
my $type = $hash->{TYPE};
Log3 ($name, 1, "$name -> BlockingCall $hash->{HELPER}{GMFRUNNING}{fn} pid:$hash->{HELPER}{AIBLOCKRUNNING}{pid} aborted: $cause");
delete $hash->{HELPER}{GMFRUNNING};
return;
}
##########################################################################
# Mitteilungssystem füllen
# Schweregrad SV:
# 0 - keine Mitteilung
# 1 - Mitteilung
# 2 - Warnung
# 3 - Fehler / Problem
#
# Statusspeicher:
# $data{$name}{messages}{999999}{RD}: 1 - gelesen, 0 - ungelesen
# $data{$name}{messages}{999000}{TS}: Timestamp Stand Message File
# $data{$name}{messages}{999500}{TS}: Timestamp Stand prepared Messages
##########################################################################
sub fillupMessageSystem {
my $paref = shift;
my $hash = $paref->{hash};
my $name = $paref->{name};
my $lang = $paref->{lang};
my $otxt = q{};
my $ntxt = q{};
my $midx = 0;
my $max_sv = 0;
## Aufnahme Stand für alt/neu Vergleich + Clear Messages
##########################################################
for my $idx (sort keys %{$data{$name}{messages}}) {
next if($idx >= $idxlimit);
$otxt .= $data{$name}{messages}{$idx}{SV} if(defined $data{$name}{messages}{$idx}{SV});
$otxt .= $data{$name}{messages}{$idx}{DE} if(defined $data{$name}{messages}{$idx}{DE});
$otxt .= $data{$name}{messages}{$idx}{EN} if(defined $data{$name}{messages}{$idx}{EN});
delete $data{$name}{messages}{$idx};
}
## Messages füllen
########################################################################
# Integration File Messages
for my $mfi (sort keys %{$data{$name}{filemessages}}) {
next if($mfi >= $idxlimit);
$midx++;
$data{$name}{messages}{$midx}{SV} = $data{$name}{filemessages}{$mfi}{SV};
$data{$name}{messages}{$midx}{DE} = $data{$name}{filemessages}{$mfi}{DE};
$data{$name}{messages}{$midx}{EN} = $data{$name}{filemessages}{$mfi}{EN};
}
# Integration prepared Messages
for my $smi (sort keys %{$data{$name}{preparedmessages}}) {
next if($smi >= $idxlimit);
$midx++;
$data{$name}{messages}{$midx}{SV} = $data{$name}{preparedmessages}{$smi}{SV};
$data{$name}{messages}{$midx}{DE} = $data{$name}{preparedmessages}{$smi}{DE};
$data{$name}{messages}{$midx}{EN} = $data{$name}{preparedmessages}{$smi}{EN};
}
$data{$name}{messages}{999000}{TS} = $data{$name}{filemessages}{999000}{TS} // 0;
$data{$name}{messages}{999500}{TS} = $data{$name}{preparedmessages}{999500}{TS} // 0;
########################################################################
## Ende Messages auffüllen
## Vergleich auf geänderte Messages
#####################################
for my $idx (sort keys %{$data{$name}{messages}}) {
next if($idx >= $idxlimit);
$ntxt .= $data{$name}{messages}{$idx}{SV} if(defined $data{$name}{messages}{$idx}{SV});
$ntxt .= $data{$name}{messages}{$idx}{DE} if(defined $data{$name}{messages}{$idx}{DE});
$ntxt .= $data{$name}{messages}{$idx}{EN} if(defined $data{$name}{messages}{$idx}{EN});
}
if ($ntxt ne $otxt) { # es gibt neue Post! bzw. Änderungen -> Read-Bit läschen
delete $data{$name}{messages}{999999}{RD};
}
if ($midx && !defined $data{$name}{messages}{999999}{RD}) { # RD = Read-Bit (undef -> Messages nicht gelesen)
my @aidx = map { $_ } (1..$midx); # größte vorhandene Severity finden ...
my @values = map { $data{$name}{messages}{$_}{SV} } @aidx;
$max_sv = max(@values);
}
my $max_icon = $svicons{$max_sv}; # ... und das dazugehörige Icon
return ($max_icon, $midx);
}
################################################################
# Ausgabe des Mitteilungsystems
################################################################
sub outputMessages {
my $paref = shift;
my $name = $paref->{name};
my $lang = $paref->{lang};
my ($micon, $midx) = fillupMessageSystem ($paref); # Ergebnisse füllen (sind leer wenn Browser nicht refreshed)
my $tnf = $data{$name}{messages}{999000}{TS} ?
(timestampToTimestring ($data{$name}{messages}{999000}{TS}, $lang))[0] :
'n.a.';
my $tpm = $data{$name}{messages}{999500}{TS} ?
(timestampToTimestring ($data{$name}{messages}{999500}{TS}, $lang))[0] :
'n.a.';
## Ausgabe
############
my $out = qq{<html>};
$out .= qq{<b>$hqtxt{msgsys}{$lang}</b> <br><br>};
$out .= qq{$hqtxt{ludich}{$lang} - <b>File:</b> $tnf, <b>System:</b> $tpm <br>};
$out .= qq{($hqtxt{dmgsig}{$lang}) <br><br>};
$out .= qq{<table class="roomoverview" style="text-align:left; border:1px solid; padding:5px; border-spacing:5px; margin-left:auto; margin-right:auto;">};
$out .= qq{<tr style="font-weight:bold;">};
$out .= qq{<td style="text-decoration:underline;"> Pos. </td>};
$out .= qq{<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>};
$out .= qq{<td style="text-decoration:underline;"> $hqtxt{msgimp}{$lang} </td>};
$out .= qq{<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>};
$out .= qq{<td style="text-decoration:underline;"> $hqtxt{simsg}{$lang} </td>};
$out .= qq{</tr>};
$out .= qq{<tr></tr>};
my $hc = 0;
for my $key (sort keys %{$data{$name}{messages}}) {
next if($key >= $idxlimit);
$hc++;
my $enmsg = encode ("utf8", $data{$name}{messages}{$key}{$lang});
$out .= qq{<tr>};
$out .= qq{<td style="padding: 5px; text-align: center"> $key </td>};
$out .= qq{<td style="padding: 5px;"> </td>};
$out .= qq{<td style="padding: 5px; text-align: center"> $data{$name}{messages}{$key}{SV} </td>};
$out .= qq{<td style="padding: 5px;"> </td>};
$out .= qq{<td style="padding-right: 5px; text-align: left"> $enmsg </td>};
$out .= qq{</tr>};
if ($hc < $midx) { # Zwischenzeile
$out .= qq{<tr>};
$out .= qq{<td> &nbsp; </td>};
$out .= qq{</tr>};
}
}
$out .= qq{</table>};
$out .= qq{</html>};
$out .= "<br>";
return $out;
}
###############################################################
# Eintritt in den KI Train Prozess normal/Blocking
###############################################################
@ -16688,7 +16927,6 @@ return;
sub aiTrain { ## no critic "not used"
my $paref = shift;
my $name = $paref->{name};
my $type = $paref->{type};
my $block = $paref->{block} // 0;
my $hash = $defs{$name};
@ -18188,63 +18426,6 @@ sub _writeAsCsv {
return "The memory structure was written to the file $outfile";
}
################################################################
# Ausgabe des Mitteilungsystems
################################################################
sub outputMessages {
my $paref = shift;
my $name = $paref->{name};
my $lang = $paref->{lang};
my ($micon, $midx) = __fillupMessages ($paref); # Ergebnisse füllen (sind leer wenn Browser nicht refreshed)
## Ausgabe
############
my $out = qq{<html>};
$out .= qq{<b>}.$hqtxt{msgsys}{$lang}.qq{</b> <br>};
$out .= qq{<b>Hinweis:</b> Gelesene Mitteilungen werden bis zu einem FHEM Neustart nicht wieder signalisiert, bleiben jedoch bei Relevanz erhalten. <br><br>};
$out .= qq{<table class="roomoverview" style="text-align:left; border:1px solid; padding:5px; border-spacing:5px; margin-left:auto; margin-right:auto;">};
$out .= qq{<tr style="font-weight:bold;">};
$out .= qq{<td style="text-decoration:underline;"> $hqtxt{number}{$lang} </td>};
$out .= qq{<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>};
$out .= qq{<td style="text-decoration:underline;"> $hqtxt{msgimp}{$lang} </td>};
$out .= qq{<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>};
$out .= qq{<td style="text-decoration:underline;"> $hqtxt{simsg}{$lang} </td>};
$out .= qq{</tr>};
$out .= qq{<tr></tr>};
my $hc = 0;
for my $key (sort keys %{$data{$name}{messages}}) {
next if($key == 9999);
$hc++;
my $enmsg = encode ("utf8", $data{$name}{messages}{$key}{$lang});
$out .= qq{<tr>};
$out .= qq{<td style="padding: 5px; text-align: center"> $key </td>};
$out .= qq{<td style="padding: 5px;"> </td>};
$out .= qq{<td style="padding: 5px; text-align: center"> $data{$name}{messages}{$key}{SV} </td>};
$out .= qq{<td style="padding: 5px;"> </td>};
$out .= qq{<td style="padding-right: 5px; text-align: left"> $enmsg </td>};
$out .= qq{</tr>};
if ($hc < $midx) { # Zwischenzeile
$out .= qq{<tr>};
$out .= qq{<td> &nbsp; </td>};
$out .= qq{</tr>};
}
}
$out .= qq{</table>};
$out .= qq{</html>};
$out .= "<br>";
return $out;
}
################################################################
# validiert die aktuelle Anlagenkonfiguration
################################################################
@ -18986,6 +19167,65 @@ sub medianArray {
return;
}
################################################################
# Berechnen 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);
}
################################################################
# Zeit gemäß DWD_OpenData-Format
# Berechnen Tag / Stunden Verschieber ab aktuellen Tag
# Input: YYYY-MM-DD HH:MM:SS
# Output: $fd - 0 (Heute), 1 (Morgen), 2 (Übermorgen), ....
# $fh - Stunde von $fd ohne führende Null
# Return: fc${fd}_${fh}
################################################################
sub formatWeatherTimestrg {
my $date = shift // return;
my $cdate = strftime "%Y-%m-%d", localtime(time);
my $refts = timestringToTimestamp ($cdate.' 00:00:00'); # Referenztimestring
my $datts = timestringToTimestamp ($date);
my $fd = int (($datts - $refts) / 86400);
my $fh = int ((split /[ :]/, $date)[1]);
return "fc${fd}_${fh}";
}
################################################################
# 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);
}
################################################################
# Timestrings berechnen
# gibt Zeitstring in lokaler Zeit zurück
@ -20027,7 +20267,7 @@ return $is;
# reading: Device ist im Reading Value enthalten
# attr: Device ist im Attr Value enthalten
# string: Device ist im Objekt-Inhalt enthalten
# return: $valid - ist die Angabe valide (1)
# return: $err - evtl. Fehler
# $a->[0] - das extrahierte Device
# $h - Hash der geparsten Entität
#####################################################################
@ -20055,23 +20295,26 @@ sub isDeviceValid {
my ($a, $h) = parseParams ($dev);
if ($a->[0] && $a->[0] =~ /\@/xs ) { # Remote Device
$a->[0] = (split '@', $a->[0])[0];
return ($err, $a->[0], $h); # ToDo: $h aus remote Werten anreichern
}
my ($dv, $al) = !$a->[0] ? ('', '') :
$a->[0] =~ /:/xs ? (split ':', $a->[0]) :
($a->[0], ''); # (optionalen) SF-spezifischen Alias abtrennen
if (!$a->[0] || !$defs{$a->[0]}) {
$a->[0] //= '';
$err = qq{The device '$a->[0]' doesn't exist or is not a valid device.};
$err = qq{There is no device set. Check the syntax with the command reference.} if(!$a->[0]);
$err = qq{The device '$a->[0]' doesn't exist anymore! Delete or change the attribute '$obj'.} if(!$defs{$a->[0]} && $method eq 'attr' && $obj =~ /consumer/);
if (!$dv || !$defs{$dv}) {
$dv //= '';
$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{The device '$dv' doesn't exist anymore! Delete or change the attribute '$obj'.} if(!$defs{$dv} && $method eq 'attr' && $obj =~ /consumer/);
}
if ($err) {
Log3 ($name, 1, "$name - ERROR - $err");
}
return ($err, $a->[0], $h);
if ($al) { # Leerzeichen im SF-Alias generieren
$al =~ s/\+/ /g;
}
return ($err, $dv, $h, $al);
}
#####################################################################
@ -22619,7 +22862,7 @@ to ensure that the system configuration is correct.
<br>
<a id="SolarForecast-attr-consumer" data-pattern="consumer.*"></a>
<li><b>consumerXX &lt;Device Name&gt; type=&lt;type&gt; power=&lt;power&gt; [switchdev=&lt;device&gt;]<br>
<li><b>consumerXX &lt;Device&gt;[:&lt;Alias&gt;] type=&lt;type&gt; power=&lt;power&gt; [switchdev=&lt;device&gt;]<br>
[mode=&lt;mode&gt;] [icon=&lt;Icon&gt;[@&lt;Color&gt;]] [mintime=&lt;minutes&gt; | SunPath[:&lt;Offset_Sunrise&gt;:&lt;Offset_Sunset&gt;]] <br>
[on=&lt;command&gt;] [off=&lt;command&gt;] [swstate=&lt;Readingname&gt;:&lt;on-Regex&gt;:&lt;off-Regex&gt] [asynchron=&lt;Option&gt] <br>
[notbefore=&lt;Expression&gt;] [notafter=&lt;Expression&gt;] [locktime=&lt;offlt&gt;[:&lt;onlt&gt;]] <br>
@ -22628,8 +22871,8 @@ to ensure that the system configuration is correct.
[surpmeth=&lt;Option&gt] [interruptable=&lt;Option&gt] [noshow=&lt;Option&gt] [exconfc=&lt;Option&gt] </b><br>
<br>
Registers a consumer &lt;Device Name&gt; with the SolarForecast Device. In this case, &lt;Device Name&gt;
is a consumer device already created in FHEM, e.g. a switchable socket.
Registers a consumer &lt;Device&gt; with the SolarForecast Device. An optional alias can be specified. <br>
In this case, &lt;Device&gt; is a consumer device already created in FHEM, e.g. a switchable socket.
Most of the keys are optional, but are a prerequisite for certain functionalities and are filled with
default values. <br>
If the dish is defined "auto", the automatic mode in the integrated consumer graphic can be switched with the
@ -22664,6 +22907,11 @@ to ensure that the system configuration is correct.
<ul>
<table>
<colgroup> <col width="12%"> <col width="88%"> </colgroup>
<tr><td> <b>Device</b> </td><td>Consumer device. In the simple case, the device works both as an energy meter and as a switch. </td></tr>
<tr><td> </td><td>In the optional alias, spaces must be replaced by '+' (e.g. 'Ein+toller+Alias'). </td></tr>
<tr><td> </td><td>If the consumer consists of different devices/channels (e.g. Homematic), the energy meter is defined as a &lt;Device&gt;. </td></tr>
<tr><td> </td><td>The associated switching device is specified with the key 'switchdev'. </td></tr>
<tr><td> </td><td> </td></tr>
<tr><td> <b>type</b> </td><td>Type of consumer. The following types are allowed: </td></tr>
<tr><td> </td><td><b>dishwasher</b> - Consumer is a dishwasher </td></tr>
<tr><td> </td><td><b>dryer</b> - Consumer is a tumble dryer </td></tr>
@ -25104,7 +25352,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
<br>
<a id="SolarForecast-attr-consumer" data-pattern="consumer.*"></a>
<li><b>consumerXX &lt;Device Name&gt; type=&lt;type&gt; power=&lt;power&gt; [switchdev=&lt;device&gt;]<br>
<li><b>consumerXX &lt;Device&gt;[:&lt;Alias&gt;] type=&lt;type&gt; power=&lt;power&gt; [switchdev=&lt;device&gt;]<br>
[mode=&lt;mode&gt;] [icon=&lt;Icon&gt;[@&lt;Farbe&gt;]] [mintime=&lt;minutes&gt; | SunPath[:&lt;Offset_Sunrise&gt;:&lt;Offset_Sunset&gt;]] <br>
[on=&lt;Kommando&gt;] [off=&lt;Kommando&gt;] [swstate=&lt;Readingname&gt;:&lt;on-Regex&gt;:&lt;off-Regex&gt] [asynchron=&lt;Option&gt] <br>
[notbefore=&lt;Ausdruck&gt;] [notafter=&lt;Ausdruck&gt;] [locktime=&lt;offlt&gt;[:&lt;onlt&gt;]] <br>
@ -25113,8 +25361,8 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
[surpmeth=&lt;Option&gt] [interruptable=&lt;Option&gt] [noshow=&lt;Option&gt] [exconfc=&lt;Option&gt] </b><br>
<br>
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.
Registriert einen Verbraucher &lt;Device&gt; beim SolarForecast Device. Ein optionaler Alias kann angegeben werden. <br>
Dabei ist &lt;Device&gt; ein in FHEM bereits angelegtes Verbraucher Device, z.B. eine Schaltsteckdose.
Die meisten Schlüssel sind optional, sind aber für bestimmte Funktionalitäten Voraussetzung und werden mit
default-Werten besetzt. <br>
Ist der Schüssel "auto" definiert, kann der Automatikmodus in der integrierten Verbrauchergrafik mit den
@ -25148,6 +25396,11 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
<ul>
<table>
<colgroup> <col width="12%"> <col width="88%"> </colgroup>
<tr><td> <b>Device</b> </td><td>Verbraucher-Gerät. Im einfachen Fall arbeitet das Gerät sowohl als Energiemesser als auch als Schalter. </td></tr>
<tr><td> </td><td>Im optionalen Alias sind Leerzeichen durch '+' zu ersetzen (z.B. 'Ein+toller+Alias'). </td></tr>
<tr><td> </td><td>Besteht der Verbraucher aus verschiedenen Geräten/Kanäalen (z.B. Homematic), wird der Energiemesser als &lt;Device&gt; definiert. </td></tr>
<tr><td> </td><td>Das dazugehörige Schalt-Gerät wird mit dem Schlüssel 'switchdev' spezifiziert. </td></tr>
<tr><td> </td><td> </td></tr>
<tr><td> <b>type</b> </td><td>Typ des Verbrauchers. Folgende Typen sind erlaubt: </td></tr>
<tr><td> </td><td><b>dishwasher</b> - Verbraucher ist eine Spülmaschine </td></tr>
<tr><td> </td><td><b>dryer</b> - Verbraucher ist ein Wäschetrockner </td></tr>