mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-01-31 12:49:34 +00:00
ac20023eac
git-svn-id: https://svn.fhem.de/fhem/trunk@14888 2b470e98-0d58-463d-a4d8-8e2adae1ed80
367 lines
11 KiB
Perl
367 lines
11 KiB
Perl
##############################################
|
|
# $Id$
|
|
package main;
|
|
|
|
use strict;
|
|
use warnings;
|
|
use IO::File;
|
|
use Blocking;
|
|
|
|
#####################################
|
|
sub
|
|
SingleFileLog_Initialize($)
|
|
{
|
|
my ($hash) = @_;
|
|
|
|
$hash->{DefFn} = "SingleFileLog_Define";
|
|
$hash->{NotifyFn} = "SingleFileLog_Log";
|
|
$hash->{AttrFn} = "SingleFileLog_Attr";
|
|
|
|
no warnings 'qw';
|
|
my @attrList = qw(
|
|
addStateEvent:1,0
|
|
disable:1,0
|
|
disabledForIntervals
|
|
dosLineEnding:1,0
|
|
numberFormat
|
|
readySuffix
|
|
syncAfterWrite:1,0
|
|
template:textField-long
|
|
writeInBackground:1,0
|
|
);
|
|
use warnings 'qw';
|
|
$hash->{AttrList} = join(" ", @attrList);
|
|
}
|
|
|
|
|
|
#####################################
|
|
sub
|
|
SingleFileLog_Define($@)
|
|
{
|
|
my ($hash, $def) = @_;
|
|
my @a = split("[ \t][ \t]*", $def);
|
|
my $fh;
|
|
|
|
return "wrong syntax: define <name> SingleFileLog filename regexp"
|
|
if(int(@a) != 4);
|
|
|
|
return "Bad regexp: starting with *" if($a[3] =~ m/^\*/);
|
|
eval { "Hallo" =~ m/^$a[3]$/ };
|
|
return "Bad regexp: $@" if($@);
|
|
|
|
$hash->{FH} = $fh;
|
|
$hash->{REGEXP} = $a[3];
|
|
$hash->{LOGFILE} =
|
|
($a[2] =~ m/^{.*}$/ ? AnalyzePerlCommand(undef, $a[2]) : $a[2]);
|
|
$hash->{STATE} = "active";
|
|
readingsSingleUpdate($hash, "filecount", 0, 0);
|
|
notifyRegexpChanged($hash, $a[3]);
|
|
|
|
return undef;
|
|
}
|
|
|
|
|
|
#####################################
|
|
sub
|
|
SingleFileLog_Log($$)
|
|
{
|
|
# Log is my entry, Dev is the entry of the changed device
|
|
my ($log, $dev) = @_;
|
|
return if($log->{READONLY});
|
|
|
|
my $ln = $log->{NAME};
|
|
return if(IsDisabled($ln));
|
|
my $events = deviceEvents($dev, AttrVal($ln, "addStateEvent", 0));
|
|
return if(!$events);
|
|
|
|
my $n = $dev->{NAME};
|
|
my $re = $log->{REGEXP};
|
|
my $max = int(@{$events});
|
|
my $tn = $dev->{NTFY_TRIGGERTIME};
|
|
my $ct = $dev->{CHANGETIME};
|
|
|
|
for (my $i = 0; $i < $max; $i++) {
|
|
my $s = $events->[$i];
|
|
$s = "" if(!defined($s));
|
|
my $t = (($ct && $ct->[$i]) ? $ct->[$i] : $tn);
|
|
if($n =~ m/^$re$/ || "$n:$s" =~ m/^$re$/ || "$t:$n:$s" =~ m/^$re$/) {
|
|
my $fc = ReadingsVal($ln,"filecount",0)+1;
|
|
readingsSingleUpdate($log, "filecount", $fc, 0);
|
|
|
|
my %arg = (log=>$log, dev=>$dev, evt=>$s);
|
|
if(AttrVal($ln, "writeInBackground",0)) {
|
|
BlockingCall("SingleFileLog_Write", \%arg);
|
|
} else {
|
|
SingleFileLog_Write(\%arg);
|
|
}
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
###################################
|
|
sub
|
|
SingleFileLog_Write($)
|
|
{
|
|
my ($ptr) = @_;
|
|
my ($log, $dev, $EVENT) = ($ptr->{log}, $ptr->{dev}, $ptr->{evt});
|
|
my $NAME = $dev->{NAME};
|
|
|
|
my $ln = $log->{NAME};
|
|
my ($seconds, $microseconds) = gettimeofday();
|
|
|
|
my @time = localtime($seconds);
|
|
my $f = $log->{LOGFILE};
|
|
my $fc = ReadingsVal($ln,"filecount",0);
|
|
$f =~ s/%Q/$fc/g;
|
|
$f = ResolveDateWildcards($f, @time);
|
|
Log3 $ln, 4, "$ln: Writing $f";
|
|
|
|
my $time = $dev->{NTFY_TRIGGERTIME};
|
|
my $time14 = sprintf("%04d%02d%02d%02d%02d%02d",
|
|
$time[5]+1900,$time[4]+1,$time[3],$time[2],$time[1],$time[0]);
|
|
my $time16 = $time14.sprintf("%02d", $microseconds/100000);
|
|
my ($decl,$idx) = ("",0);
|
|
my $nf = AttrVal($ln, "numberFormat", "%1.6E");
|
|
foreach my $part (split(" ", $EVENT)) {
|
|
$decl .= "my \$EVTPART$idx='$part';";
|
|
$decl .= "my \$EVTNUM$idx='".sprintf($nf,$part)."';"
|
|
if(looks_like_number($part));
|
|
$idx++;
|
|
}
|
|
|
|
my $template = AttrVal($ln, "template", '$time $NAME $EVENT\n');
|
|
$template = "\"$template\"" if($template !~ m/^{.*}$/);
|
|
|
|
my $data = eval "$decl $template";
|
|
if($@) {
|
|
Log3 $ln, 1, "$ln: error evaluating template: $@";
|
|
return;
|
|
}
|
|
$data =~ s/\n/\r\n/mg if(AttrVal($ln, "dosLineEnding", 0));
|
|
|
|
my $fh = new IO::File ">>$f.tmp";
|
|
if(!defined($fh)) {
|
|
Log3 $ln, 1, "$ln: Can't open $f.tmp: $!";
|
|
return;
|
|
}
|
|
print $fh $data;
|
|
$fh->sync if($^O ne 'MSWin32' && AttrVal($ln, "syncAfterWrite", 0));
|
|
close($fh);
|
|
|
|
my $sfx = AttrVal($ln, "readySuffix", ".rdy");
|
|
$sfx = "" if(lc($sfx) eq "none");
|
|
if($sfx ne ".tmp") {
|
|
Log3 $ln, 1, "Cannot rename $f.tmp to $f$sfx" if(!rename "$f.tmp","$f$sfx");
|
|
}
|
|
}
|
|
|
|
###################################
|
|
sub
|
|
SingleFileLog_Attr(@)
|
|
{
|
|
my @a = @_;
|
|
my $do = 0;
|
|
|
|
if($a[0] eq "set" && $a[2] eq "disable") {
|
|
$do = (!defined($a[3]) || $a[3]) ? 1 : 2;
|
|
}
|
|
$do = 2 if($a[0] eq "del" && (!$a[2] || $a[2] eq "disable"));
|
|
return if(!$do);
|
|
|
|
$defs{$a[1]}{STATE} = ($do == 1 ? "disabled" : "active");
|
|
|
|
return undef;
|
|
}
|
|
|
|
1;
|
|
|
|
=pod
|
|
=item helper
|
|
=item summary write single events to a separate file each, using templates
|
|
=item summary_DE schreibt einzelne Events in separate Dateien via templates
|
|
=begin html
|
|
|
|
<a name="SingleFileLog"></a>
|
|
<h3>SingleFileLog</h3>
|
|
<ul>
|
|
<br>
|
|
|
|
<a name="SingleFileLogdefine"></a>
|
|
<b>Define</b>
|
|
<ul>
|
|
<code>define <name> SingleFileLog <filename> <regexp>
|
|
</code>
|
|
<br><br>
|
|
For each event or devicename:event matching the <regexp> create a
|
|
new file <filename><br>
|
|
<code><filename></code> may contain %-wildcards of the
|
|
POSIX strftime function of the underlying OS (see your strftime manual),
|
|
additionally %Q is replaced with a sequential number unique to the
|
|
SingleFileLog device. The file content is based on the template attribute,
|
|
see below.<br>
|
|
If the filename is enclosed in {} then it is evaluated as a perl expression,
|
|
which can be used to use a global path different from %L.
|
|
</ul>
|
|
<br>
|
|
|
|
<a name="SingleFileLogset"></a>
|
|
<b>Set</b> <ul>N/A</ul><br>
|
|
|
|
<a name="SingleFileLogget"></a>
|
|
<b>Get</b> <ul>N/A</ul><br>
|
|
|
|
<a name="SingleFileLogattr"></a>
|
|
<b>Attributes</b>
|
|
<ul>
|
|
<li><a href="#addStateEvent">addStateEvent</a></li><br>
|
|
<li><a href="#disable">disable</a></li>
|
|
<li><a href="#disabledForIntervals">disabledForIntervals</a></li><br>
|
|
|
|
<li><a href="#dosLineEnding">dosLineEnding</a><br>
|
|
Create the file with the line-terminator used on Windows systems (\r\n).
|
|
</li><br>
|
|
|
|
<li><a name="#numberFormat">numberFormat</a><br>
|
|
If a word in the event looks like a number, then it is reformatted using
|
|
the numberFormat, and $EVTNUMx is set (analogue to $EVTPARx). Default is
|
|
%1.6E, see the printf manual for details.
|
|
</li><br>
|
|
|
|
<li><a name="#readySuffy">readySuffix</a><br>
|
|
The file specified in the definition will be created with the suffix .tmp
|
|
and after the file is closed, will be renamed to the value of this
|
|
attribute. Default is .rdy, specify none to remove the suffix completely.
|
|
</li><br>
|
|
|
|
<li><a name="#syncAfterWrite">syncAfterWrite</a><br>
|
|
Force the operating system to write the contents to the disc if set to 1.
|
|
Note: this can slow down the writing, and may shorten the life of SSDs.
|
|
Defalt is 0 (off)
|
|
</li><br>
|
|
|
|
<li><a name="#template">template</a><br>
|
|
This attribute specifies the content of the file. Following variables
|
|
are replaced before writing the file:
|
|
<ul>
|
|
<li>$EVENT - the complete event</li>
|
|
<li>$EVTPART0 $EVTPART1 ... - the event broken into single words</li>
|
|
<li>$EVTNUM0 $EVTNUM1 ... - reformatted as numbers, see numberFormat
|
|
above</li>
|
|
<li>$NAME - the name of the device generating the event</li>
|
|
<li>$time - the current time, formatted as YYYY-MM-DD HH:MM:SS</li>
|
|
<li>$time14 - the current time, formatted as YYYYMMDDHHMMSS</li>
|
|
<li>$time16 - the current time, formatted as YYYYMMDDHHMMSSCC,
|
|
where CC is the hundredth second</li>
|
|
</ul>
|
|
If the template is enclosed in {} than it will be evaluated as a perl
|
|
expression, and its result is written to the file.<br>
|
|
Default is $time $NAME $EVENT\n
|
|
</li><br>
|
|
|
|
<li><a name="#writeInBackground">writeInBackground</a><br>
|
|
if set (to 1), the writing will occur in a background process to avoid
|
|
blocking FHEM. Default is 0.
|
|
</li><br>
|
|
</ul>
|
|
<br>
|
|
</ul>
|
|
|
|
=end html
|
|
|
|
=begin html_DE
|
|
|
|
<a name="SingleFileLog"></a>
|
|
<h3>SingleFileLog</h3>
|
|
<ul>
|
|
<br>
|
|
|
|
<a name="SingleFileLogdefine"></a>
|
|
<b>Define</b>
|
|
<ul>
|
|
<code>define <name> SingleFileLog <filename> <regexp>
|
|
</code>
|
|
<br><br>
|
|
Fü jedes Event oder Gerätename:Event, worauf <regexp>
|
|
zutrifft, wird eine separate Datei angelegt, der Inhalt wird von dem
|
|
template Attribut gesteuert (s.u.).
|
|
<code><filename></code> kann %-Wildcards der POSIX strftime-Funktion
|
|
des darunterliegenden OS enthalten (siehe auch man strftime).
|
|
Zusätzlich wird %Q durch eine fortlaufende Zahl ersetzt.<br>
|
|
Falls filename in {} eingeschlossen ist, dann wird sie als perl-Ausdruck
|
|
ausgewertet, was ermöglicht einen vom %L abweichenden globalem Pfad zu
|
|
definieren.
|
|
</ul>
|
|
<br>
|
|
|
|
<a name="SingleFileLogset"></a>
|
|
<b>Set</b> <ul>N/A</ul><br>
|
|
|
|
<a name="SingleFileLogget"></a>
|
|
<b>Get</b> <ul>N/A</ul><br>
|
|
|
|
<a name="SingleFileLogattr"></a>
|
|
<b>Attribute</b>
|
|
<ul>
|
|
<li><a href="#addStateEvent">addStateEvent</a></li><br>
|
|
<li><a href="#disable">disable</a></li>
|
|
<li><a href="#disabledForIntervals">disabledForIntervals</a></li><br>
|
|
|
|
<li><a href="#dosLineEnding">dosLineEnding</a><br>
|
|
Erzeugt die Datei mit der auf Windows Systemen ueblichen Zeilenenden
|
|
(\r\n)
|
|
</li><br>
|
|
|
|
<li><a name="#numberFormat">numberFormat</a><br>
|
|
Falls ein Wort im Event wie eine Zahl aussieht, dann wird es wie in
|
|
numberFormat angegeben, umformatiert, und als $EVTNUMx (analog zu
|
|
$EVTPARTx) zur Verfügung gestellt. Voreinstellung ist %1.6E, siehe
|
|
die printf Formatbeschreibung für Details.
|
|
</li><br>
|
|
|
|
<li><a name="#readySuffy">readySuffix</a><br>
|
|
Die in der Definition spezifizierte Datei wird mit dem Suffix .tmp
|
|
angelegt, und nachdem sie fertig ist, mit readySuffix umbenannt.
|
|
Die Voreinstellung ist .rdy; um kein Suffix zu erzeugen, muss man none
|
|
spezifizieren.
|
|
</li><br>
|
|
|
|
<li><a name="#syncAfterWrite">syncAfterWrite</a><br>
|
|
Zwingt das Betriebssystem die Datei sofort auf die Platte zu schreiben.
|
|
Achtung: das kann die Geschwindigkeit beeinträchtigen, und bei SSDs
|
|
zu einem früheren Ausfall führen. Die Voreinstellung ist 0
|
|
(aus).
|
|
</li><br>
|
|
|
|
<li><a name="#template">template</a><br>
|
|
Damit wird der Inhalt der geschriebenen Datei spezifiziert. Folgende
|
|
Variablen werden vor dem Schreiben der Datei ersetzt:
|
|
<ul>
|
|
<li>$EVENT - das vollständige Event.</li>
|
|
<li>$EVTPART0 $EVTPART1 ... - die einzelnen Wörter des Events.</li>
|
|
<li>$EVTNUM0 $EVTNUM1 ... - umformatiert als Zahl, siehe numberFormat
|
|
weiter oben.</li>
|
|
<li>$NAME - der Name des Gerätes, das das Event generiert.</li>
|
|
<li>$time - die aktuelle Zeit, formatiert als YYYY-MM-DD HH:MM:SS</li>
|
|
<li>$time14 - die aktuelle Zeit, formatiert als YYYYMMDDHHMMSS</li>
|
|
<li>$time16 - die aktuelle Zeit, formatiert als YYYYMMDDHHMMSSCC,
|
|
wobei CC die hundertstel Sekunde ist</li>
|
|
</ul>
|
|
Falls template in {} eingeschlossen ist, dann wird er als perl-Ausdruck
|
|
ausgefuehrt, und das Ergebnis wird in die Datei geschrieben.<br>
|
|
Die Voreinstellung ist $time $NAME $EVENT\n
|
|
</li><br>
|
|
|
|
<li><a name="#writeInBackground">writeInBackground</a><br>
|
|
falls gesetzt (auf 1), dann erfolgt das Schreiben in einem
|
|
Hintergrundprozess, um FHEM nicht zu blockieren. Die Voreinstellung ist 0
|
|
(aus).
|
|
</li><br>
|
|
</ul>
|
|
<br>
|
|
</ul>
|
|
|
|
=end html_DE
|
|
|
|
=cut
|