######################################################################### # fhem Modul für Geräte mit Web-Oberfläche # wie z.B. Poolmanager Pro von Bayrol (PM5) # # This file is part of fhem. # # Fhem is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # Fhem is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with fhem. If not, see . # ############################################################################## # Changelog: # # 2013-12-25 initial version # 2013-12-29 modified to use non blocking HTTP # 2014-1-1 modified to use attr instead of set to define internal parameters # 2014-1-6 extended error handling and added documentation # 2014-1-15 added readingsExpr to allow some computation on raw values before put in readings # 2014-3-13 added noShutdown and disable attributes package main; use strict; use warnings; use Time::HiRes qw(gettimeofday); use HttpUtils; sub HTTPMOD_Initialize($); sub HTTPMOD_Define($$); sub HTTPMOD_Undef($$); sub HTTPMOD_Set($@); sub HTTPMOD_Get($@); sub HTTPMOD_Attr(@); sub HTTPMOD_GetUpdate($); sub HTTPMOD_Read($$$); # # lists of Set and Get Options for this module # so far this is not used my %HTTPMOD_sets = ( ); my %HTTPMOD_gets = ( ); # # FHEM module intitialisation # defines the functions to be called from FHEM ######################################################################### sub HTTPMOD_Initialize($) { my ($hash) = @_; $hash->{DefFn} = "HTTPMOD_Define"; $hash->{UndefFn} = "HTTPMOD_Undef"; #$hash->{SetFn} = "HTTPMOD_Set"; #$hash->{GetFn} = "HTTPMOD_Get"; $hash->{AttrFn} = "HTTPMOD_Attr"; $hash->{AttrList} = "do_not_notify:1,0 " . "readingsName.* " . "readingsRegex.* " . "readingsExpr.* " . "requestHeader.* " . "requestData.* " . "disable:0,1 " . "noShutdown:0,1 " . $readingFnAttributes; } # # Define command # init internal values, # set internal timer get Updates ######################################################################### sub HTTPMOD_Define($$) { my ( $hash, $def ) = @_; my @a = split( "[ \t][ \t]*", $def ); return "wrong syntax: define HTTPMOD URL interval" if ( @a < 3 ); my $name = $a[0]; my $url = $a[2]; my $inter = 300; if(int(@a) == 4) { $inter = $a[3]; if ($inter < 5) { return "interval too small, please use something > 5, default is 300"; } } $hash->{url} = $url; $hash->{Interval} = $inter; # for non blocking HTTP Get $hash->{callback} = \&HTTPMOD_Read; $hash->{timeout} = 2; #$hash->{loglevel} = 3; # initial request after 2 secs, there timer is set to interval for further update InternalTimer(gettimeofday()+2, "HTTPMOD_GetUpdate", $hash, 0); return undef; } # # undefine command when device is deleted ######################################################################### sub HTTPMOD_Undef($$) { my ( $hash, $arg ) = @_; DevIo_CloseDev($hash); RemoveInternalTimer($hash); return undef; } # # Attr command ######################################################################### sub HTTPMOD_Attr(@) { my ($cmd,$name,$aName,$aVal) = @_; # $cmd can be "del" or "set" # $name is device name # aName and aVal are Attribute name and value # Attributes are readingsRegexp.*, requestHeader.* and requestData.* # requestHeader and requestData need no special treatment here # however they have to be added to $hash later so HttpUtils # an pick them up. Maybe later versions of HttpUtils could # also pick up attributes? # readingsRegex.* needs validation though. # ... to be implemented later here ... # each readingsRegexX defines a pair of Reading and Regex if ($cmd eq "set") { if ($aName =~ "readingsRegex") { eval { qr/$aVal/ }; if ($@) { Log3 $name, 3, "HTTPOD: Invalid regex in attr $name $aName $aVal: $@"; return "Invalid Regex $aVal"; } } elsif ($aName =~ "readingsExpr") { my $val = 1; eval $aVal; if ($@) { Log3 $name, 3, "HTTPOD: Invalid Expression in attr $name $aName $aVal: $@"; return "Invalid Expression $aVal"; } } } return undef; } # # SET command # currently not used ######################################################################### sub HTTPMOD_Set($@) { my ( $hash, @a ) = @_; return "\"set HTTPMOD\" needs at least an argument" if ( @a < 2 ); # @a is an array with DeviceName, SetName, Rest of Set Line my $name = shift @a; my $attr = shift @a; my $arg = join("", @a); if(!defined($HTTPMOD_sets{$attr})) { my @cList = keys %HTTPMOD_sets; return "Unknown argument $attr, choose one of " . join(" ", @cList); } return undef; } # # GET command # currently not used ######################################################################### sub HTTPMOD_Get($@) { my ( $hash, @a ) = @_; return "\"get HTTPMOD\" needs at least an argument" if ( @a < 2 ); # @a is an array with DeviceName and GetName my $name = shift @a; my $attr = shift @a; if(!defined($HTTPMOD_gets{$attr})) { my @cList = keys %HTTPMOD_gets; return "Unknown argument $attr, choose one of " . join(" ", @cList); } return undef; } # # request new data from device ################################### sub HTTPMOD_GetUpdate($) { my ($hash) = @_; my $name = $hash->{NAME}; InternalTimer(gettimeofday()+$hash->{Interval}, "HTTPMOD_GetUpdate", $hash, 1); return if(AttrVal($name, "disable", undef)); Log3 $name, 4, "HTTPMOD: GetUpdate called, hash = $hash, name = $name"; if ( $hash->{url} eq "none" ) { return 0; } my $header = join ("\r\n", map ($attr{$name}{$_}, sort grep (/requestHeader/, keys %{$attr{$name}}))); if (length $header > 0) { $hash->{header} = $header; } else { delete $hash->{header}; } my $data = join ("\r\n", map ($attr{$name}{$_}, sort grep (/requestData/, keys %{$attr{$name}}))); if (length $data > 0) { $hash->{data} = $data; } else { delete $hash->{data}; } if (AttrVal($name, "disable", undef)) { $hash->{noshutdown} = 1; } else { delete $hash->{noshutdown}; }; HttpUtils_NonblockingGet($hash); } # # read / parse new data from device # - callback for non blocking HTTP ################################### sub HTTPMOD_Read($$$) { my ($hash, $err, $buffer) = @_; my $name = $hash->{NAME}; if ($err) { Log3 $name, 3, "HTTPMOD got error in callback: $err"; return; } Log3 $name, 5, "HTTPMOD: Callback called: Hash: $hash, Name: $name, buffer: $buffer\r\n"; my $msg = ""; readingsBeginUpdate($hash); foreach my $a (sort (grep (/readingsName/, keys %{$attr{$name}}))) { $a =~ /readingsName(.*)/; if (defined ($attr{$name}{'readingsName' . $1}) && defined ($attr{$name}{'readingsRegex' . $1})) { my $reading = $attr{$name}{'readingsName' . $1}; my $regex = $attr{$name}{'readingsRegex' . $1}; my $expr = ""; if (defined ($attr{$name}{'readingsExpr' . $1})) { $expr = $attr{$name}{'readingsExpr' . $1}; } Log3 $name, 5, "HTTPMOD: Trying to extract Reading $reading with regex /$regex/..."; if ($buffer =~ /$regex/) { my $val = $1; if ($expr) { $val = eval $expr; Log3 $name, 5, "HTTPMOD: change value for Reading $reading with Expr $expr from $1 to $val"; } Log3 $name, 5, "HTTPMOD: Set Reading $reading to $val"; readingsBulkUpdate( $hash, $reading, $val ); } else { if ($msg) { $msg .= ", $reading"; } else { $msg = "$reading"; } } } else { Log3 $name, 3, "HTTPMOD: inconsitant attributes for $a"; } } readingsEndUpdate( $hash, 1 ); if ($msg) { Log3 $name, 3, "HTTPMOD: Response didn't match Reading(s) $msg"; Log3 $name, 4, "HTTPMOD: response was $buffer"; } return; } 1; =pod =begin html

HTTPMOD

=end html =cut