98_HTTPMOD.pm: support for named reading groups

git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@16893 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
StefanStrobel 2018-06-21 17:50:16 +00:00
parent df213308bc
commit e37607eb5d

View File

@ -148,6 +148,9 @@
# 2018-01-18 added preProcessRegex e.g. to fix broken JSON data in a response # 2018-01-18 added preProcessRegex e.g. to fix broken JSON data in a response
# 2018-02-10 modify handling of attribute removeBuf since httpUtils doesn't expose its buffer anymore, # 2018-02-10 modify handling of attribute removeBuf since httpUtils doesn't expose its buffer anymore,
# Instead new attribute showBody to explicitely show a formatted version of the http response body (header is already shown) # Instead new attribute showBody to explicitely show a formatted version of the http response body (header is already shown)
# 2018-05-01 new attribute enforceGoodReadingNames
# 2018-05-05 experimental support for named groups in regexes (won't support individual MaxAge / deleteIf attributes)
# see ExtractReading function
# #
# #
@ -159,6 +162,7 @@
# you can refer to them by absolute number (using "$1" instead of "\g1" , etc) # you can refer to them by absolute number (using "$1" instead of "\g1" , etc)
# or by name via the %+ hash, using "$+{name}". # or by name via the %+ hash, using "$+{name}".
# -> if named groups exist - # -> if named groups exist -
#
# reading mit Status je get (error, no match, ...) oder reading zum nachverfolgen der schritte, fehler, auth etc. # reading mit Status je get (error, no match, ...) oder reading zum nachverfolgen der schritte, fehler, auth etc.
# #
# In _Attr bei Prüfungen auf get auch set berücksichtigen wo nötig, ebenso in der Attr Liste (oft fehlt set) # In _Attr bei Prüfungen auf get auch set berücksichtigen wo nötig, ebenso in der Attr Liste (oft fehlt set)
@ -173,6 +177,10 @@
# extend httpmod to support simple tcp connections over devio instead of HttpUtils? # extend httpmod to support simple tcp connections over devio instead of HttpUtils?
# #
# #
# Merkliste fürs nächste Fhem Release
# - enforceGoodReadingNames 1 als Default
#
#
# #
# verwendung von defptr: # verwendung von defptr:
@ -213,7 +221,7 @@ sub HTTPMOD_AddToQueue($$$$$;$$$$);
sub HTTPMOD_JsonFlatter($$;$); sub HTTPMOD_JsonFlatter($$;$);
sub HTTPMOD_ExtractReading($$$$$); sub HTTPMOD_ExtractReading($$$$$);
my $HTTPMOD_Version = '3.4.2 - 10.2.2018'; my $HTTPMOD_Version = '3.4.4 - 5.5.2018';
# #
# FHEM module intitialisation # FHEM module intitialisation
@ -346,6 +354,7 @@ sub HTTPMOD_Initialize($)
"enableCookies:0,1 " . "enableCookies:0,1 " .
"enableXPath:0,1 " . # old "enableXPath:0,1 " . # old
"enableXPath-Strict:0,1 " . # old "enableXPath-Strict:0,1 " . # old
"enforceGoodReadingNames " .
$readingFnAttributes; $readingFnAttributes;
} }
@ -511,7 +520,7 @@ sub HTTPMOD_Attr(@)
} }
} elsif ($aName =~ /Expr/) { # validate all Expressions } elsif ($aName =~ /Expr/) { # validate all Expressions
my $val = 0; my $old = 0; my $val = 0; my $old = 0;
my $timeDiff = 0; my $timeDiff = 0; # to be available in Exprs
my @matchlist = (); my @matchlist = ();
no warnings qw(uninitialized); no warnings qw(uninitialized);
my $oldSig = ($SIG{__WARN__} ? $SIG{__WARN__} : 'DEFAULT'); my $oldSig = ($SIG{__WARN__} ? $SIG{__WARN__} : 'DEFAULT');
@ -632,6 +641,7 @@ sub HTTPMOD_Attr(@)
HTTPMOD_SetTimer($hash, 2); # change timer for alignment but at least 2 secs from now HTTPMOD_SetTimer($hash, 2); # change timer for alignment but at least 2 secs from now
} elsif ($aName =~ /^(reading|get)([0-9]+)(-[0-9]+)?Name$/) { } elsif ($aName =~ /^(reading|get)([0-9]+)(-[0-9]+)?Name$/) {
# todo: validate good reading name if enforceGoodReadingNames is set to 1 / by default in next fhem version
$hash->{".updateRequestHash"} = 1; $hash->{".updateRequestHash"} = 1;
} }
@ -1702,7 +1712,7 @@ sub HTTPMOD_FormatReading($$$$$)
if ($expr) { if ($expr) {
my $old = $val; # save for later logging my $old = $val; # save for later logging
my $now = ($hash->{".updateTime"} ? $hash->{".updateTime"} : gettimeofday()); my $now = ($hash->{".updateTime"} ? $hash->{".updateTime"} : gettimeofday());
my $timeDiff = 0; my $timeDiff = 0; # to be available in Exprs
my $timeStr = ReadingsTimestamp($name, $reading, 0); my $timeStr = ReadingsTimestamp($name, $reading, 0);
$timeDiff = ($now - time_str2num($timeStr)) if ($timeStr); $timeDiff = ($now - time_str2num($timeStr)) if ($timeStr);
@ -1746,7 +1756,7 @@ sub HTTPMOD_ExtractReading($$$$$)
my ($hash, $buffer, $context, $num, $reqType) = @_; my ($hash, $buffer, $context, $num, $reqType) = @_;
# for get / set which use reading.* definitions for parsing reqType might be "get01" and context might be "reading" # for get / set which use reading.* definitions for parsing reqType might be "get01" and context might be "reading"
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my ($val, $reading, $regex) = ("", "", ""); my ($reading, $regex) = ("", "", "");
my ($json, $xpath, $xpathst, $recomb, $regopt, $sublen, $alwaysn); my ($json, $xpath, $xpathst, $recomb, $regopt, $sublen, $alwaysn);
my @subrlist = (); my @subrlist = ();
my @matchlist = (); my @matchlist = ();
@ -1762,13 +1772,14 @@ sub HTTPMOD_ExtractReading($$$$$)
# support for old syntax # support for old syntax
if ($context eq "reading") { if ($context eq "reading") {
$reading = AttrVal($name, 'readingsName'.$num, ($json ? $json : "unnamed-$num")); $reading = AttrVal($name, 'readingsName'.$num, ($json ? $json : "reading$num"));
$regex = AttrVal($name, 'readingsRegex'.$num, ""); $regex = AttrVal($name, 'readingsRegex'.$num, "");
} }
# new syntax overrides reading and regex # new syntax overrides reading and regex
$reading = HTTPMOD_GetFAttr($name, $context, $num, "Name", $reading); $reading = HTTPMOD_GetFAttr($name, $context, $num, "Name", $reading);
$regex = HTTPMOD_GetFAttr($name, $context, $num, "Regex", $regex); $regex = HTTPMOD_GetFAttr($name, $context, $num, "Regex", $regex);
my %namedRegexGroups;
if ($regex) { if ($regex) {
# old syntax for xpath and xpath-strict as prefix in regex - one result joined # old syntax for xpath and xpath-strict as prefix in regex - one result joined
@ -1797,11 +1808,15 @@ sub HTTPMOD_ExtractReading($$$$$)
Log3 $name, 5, "$name: ExtractReading $reading with regex /$regex/$regopt ..."; Log3 $name, 5, "$name: ExtractReading $reading with regex /$regex/$regopt ...";
eval '@matchlist = ($buffer =~ /' . "$regex/$regopt" . ')'; eval '@matchlist = ($buffer =~ /' . "$regex/$regopt" . ')';
Log3 $name, 3, "$name: error in regex matching with regex option: $@" if ($@); Log3 $name, 3, "$name: error in regex matching with regex option: $@" if ($@);
%namedRegexGroups = %+ if (%+);
} else { } else {
Log3 $name, 5, "$name: ExtractReading $reading with regex /$regex/..."; Log3 $name, 5, "$name: ExtractReading $reading with regex /$regex/...";
@matchlist = ($buffer =~ /$regex/); @matchlist = ($buffer =~ /$regex/);
%namedRegexGroups = %+ if (%+);
} }
Log3 $name, 5, "$name: " . @matchlist . " capture group(s), matchlist = " . join ",", @matchlist if (@matchlist); Log3 $name, 5, "$name: " . @matchlist . " capture group(s), " .
(%namedRegexGroups ? "named capture groups, " : "") .
"matchlist = " . join ",", @matchlist if (@matchlist);
} }
} elsif ($json) { } elsif ($json) {
Log3 $name, 5, "$name: ExtractReading $reading with json $json ..."; Log3 $name, 5, "$name: ExtractReading $reading with json $json ...";
@ -1847,10 +1862,6 @@ sub HTTPMOD_ExtractReading($$$$$)
my $match = @matchlist; my $match = @matchlist;
if ($match) { if ($match) {
my ($eNum, $subReading);
my $group = 1;
my $subNum = "";
if ($recomb) { if ($recomb) {
Log3 $name, 5, "$name: ExtractReading is recombining $match matches with expression $recomb"; Log3 $name, 5, "$name: ExtractReading is recombining $match matches with expression $recomb";
my $oldSig = ($SIG{__WARN__} ? $SIG{__WARN__} : 'DEFAULT'); my $oldSig = ($SIG{__WARN__} ? $SIG{__WARN__} : 'DEFAULT');
@ -1864,7 +1875,36 @@ sub HTTPMOD_ExtractReading($$$$$)
@matchlist = ($val); @matchlist = ($val);
$match = 1; $match = 1;
} }
foreach $val (@matchlist) { if (%namedRegexGroups) {
Log3 $name, 5, "$name: experimental named regex group handling";
foreach my $subReading (keys %namedRegexGroups) {
my $val = $namedRegexGroups{$subReading};
push @subrlist, $subReading;
# search for group in -Name attrs (-group is sub number) ...
my $group = 0;
foreach my $aName (sort keys %{$attr{$name}}) {
if ($aName =~ /^$context$num-([\d]+)Name$/) {
if ($attr{$name}{$context.$num."-".$1."Name"} eq $subReading) {
$group = $1;
Log3 $name, 5, "$name: ExtractReading uses $context$num-$group attrs for named capture group $subReading";
}
}
}
my $eNum = $num . ($group ? "-".$group : "");
$val = HTTPMOD_FormatReading($hash, $context, $eNum, $val, $subReading);
Log3 $name, 4, "$name: ExtractReading for $context$num sets reading for named capture group $subReading to $val";
readingsBulkUpdate( $hash, $subReading, $val );
# point from reading name back to the parsing definition as reading01 or get02 ...
$hash->{defptr}{readingBase}{$subReading} = $context; # used to find maxAge attr
$hash->{defptr}{readingNum}{$subReading} = $num; # used to find maxAge attr
$hash->{defptr}{requestReadings}{$reqType}{$subReading} = "$context $eNum"; # used by deleteOnError / deleteIfUnmatched
delete $hash->{defptr}{readingOutdated}{$subReading}; # used by MaxAge as well
}
} else {
my $group = 1;
foreach my $val (@matchlist) {
my ($subNum, $eNum, $subReading);
if ($match == 1) { if ($match == 1) {
# only one match # only one match
$eNum = $num; $eNum = $num;
@ -1891,14 +1931,15 @@ sub HTTPMOD_ExtractReading($$$$$)
Log3 $name, 4, "$name: ExtractReading for $context$num-$group sets $subReading to $val"; Log3 $name, 4, "$name: ExtractReading for $context$num-$group sets $subReading to $val";
readingsBulkUpdate( $hash, $subReading, $val ); readingsBulkUpdate( $hash, $subReading, $val );
# point from reading name back to the parsing definition as reading01 or get02 ... # point from reading name back to the parsing definition as reading01 or get02 ...
$hash->{defptr}{readingBase}{$subReading} = $context; $hash->{defptr}{readingBase}{$subReading} = $context; # used to find maxAge attr
$hash->{defptr}{readingNum}{$subReading} = $num; $hash->{defptr}{readingNum}{$subReading} = $num; # used to find maxAge attr
$hash->{defptr}{readingSubNum}{$subReading} = $subNum if ($subNum); $hash->{defptr}{readingSubNum}{$subReading} = $subNum if ($subNum); # used to find maxAge attr
$hash->{defptr}{requestReadings}{$reqType}{$subReading} = "$context $eNum"; $hash->{defptr}{requestReadings}{$reqType}{$subReading} = "$context $eNum"; # used by deleteOnError / deleteIfUnmathced
# might be get01 Temp-02 reading 5 (where its parsing / naming was defined) # might be get01 Temp-02 reading 5 (where its parsing / naming was defined)
delete $hash->{defptr}{readingOutdated}{$subReading}; delete $hash->{defptr}{readingOutdated}{$subReading}; # used by MaxAge as well
$group++; $group++;
} }
}
} else { } else {
Log3 $name, 5, "$name: ExtractReading $reading did not match" if ($try); Log3 $name, 5, "$name: ExtractReading $reading did not match" if ($try);
} }
@ -2497,16 +2538,19 @@ sub HTTPMOD_Read($$$)
# create a reading for each JSON object and use formatting options if a correspondig reading name / formatting is defined # create a reading for each JSON object and use formatting options if a correspondig reading name / formatting is defined
if (ref $hash->{ParserData}{JSON} eq "HASH") { if (ref $hash->{ParserData}{JSON} eq "HASH") {
foreach my $object (keys %{$hash->{ParserData}{JSON}}) { foreach my $object (keys %{$hash->{ParserData}{JSON}}) {
# todo: create good reading name with makeReadingName instead of using the potentially illegal object name
my $rName = $object;
$rName = makeReadingName($object) if (AttrVal($name, "enforceGoodReadingNames", 0)); # todo: should become default with next fhem version
my $value = $hash->{ParserData}{JSON}{$object}; my $value = $hash->{ParserData}{JSON}{$object};
Log3 $name, 5, "$name: Read set JSON $object as reading $object to value " . $value; Log3 $name, 5, "$name: Read set JSON $object as reading $rName to value " . $value;
$value = HTTPMOD_FormatReading($hash, $context, $num, $value, $object); $value = HTTPMOD_FormatReading($hash, $context, $num, $value, $rName);
readingsBulkUpdate($hash, $object, $value); readingsBulkUpdate($hash, $rName, $value);
push @matched, $object; # unmatched is not filled for "ExtractAllJSON" push @matched, $rName; # unmatched is not filled for "ExtractAllJSON"
delete $hash->{defptr}{readingOutdated}{$object}; delete $hash->{defptr}{readingOutdated}{$rName};
$hash->{defptr}{readingBase}{$object} = $context; $hash->{defptr}{readingBase}{$rName} = $context;
$hash->{defptr}{readingNum}{$object} = $num; $hash->{defptr}{readingNum}{$rName} = $num;
$hash->{defptr}{requestReadings}{$type}{$object} = "$context $num"; $hash->{defptr}{requestReadings}{$type}{$rName} = "$context $num";
} }
} else { } else {
Log3 $name, 3, "$name: no parsed JSON structure available"; Log3 $name, 3, "$name: no parsed JSON structure available";
@ -3573,6 +3617,10 @@ HTTPMOD_AddToQueue($$$$$;$$$$){
This attribute should no longer be used. Please specify an HTTP XPath in the dedicated attributes shown above. This attribute should no longer be used. Please specify an HTTP XPath in the dedicated attributes shown above.
<li><b>enableXPath-Strict</b></li> <li><b>enableXPath-Strict</b></li>
This attribute should no longer be used. Please specify an XML XPath in the dedicated attributes shown above. This attribute should no longer be used. Please specify an XML XPath in the dedicated attributes shown above.
<li><b>enforceGoodReadingNames</b></li>
makes sure that reading names are valid and especially that extractAllJSON creates valid reading names.
<li><b>parseFunction1</b> and <b>parseFunction2</b></li> <li><b>parseFunction1</b> and <b>parseFunction2</b></li>
These functions allow an experienced Perl / Fhem developer to plug in his own parsing functions.<br> These functions allow an experienced Perl / Fhem developer to plug in his own parsing functions.<br>
Please look into the module source to see how it works and don't use them if you are not sure what you are doing. Please look into the module source to see how it works and don't use them if you are not sure what you are doing.