diff --git a/fhem/FHEM/98_HTTPMOD.pm b/fhem/FHEM/98_HTTPMOD.pm index cec048208..407eff816 100755 --- a/fhem/FHEM/98_HTTPMOD.pm +++ b/fhem/FHEM/98_HTTPMOD.pm @@ -1,7 +1,7 @@ ######################################################################### # 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 @@ -18,15 +18,15 @@ # along with fhem. If not, see . # ############################################################################## -# Changelog: +# 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 -# - +# 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; @@ -60,21 +60,23 @@ my %HTTPMOD_gets = ( ######################################################################### sub HTTPMOD_Initialize($) { - my ($hash) = @_; + 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.* " . - $readingFnAttributes; + $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; } # @@ -84,35 +86,35 @@ sub HTTPMOD_Initialize($) ######################################################################### sub HTTPMOD_Define($$) { - my ( $hash, $def ) = @_; - my @a = split( "[ \t][ \t]*", $def ); + my ( $hash, $def ) = @_; + my @a = split( "[ \t][ \t]*", $def ); - return "wrong syntax: define HTTPMOD URL interval" - if ( @a < 3 ); + 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"; - } - } + 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); + $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; + return undef; } # @@ -120,10 +122,10 @@ sub HTTPMOD_Define($$) ######################################################################### sub HTTPMOD_Undef($$) { - my ( $hash, $arg ) = @_; - DevIo_CloseDev($hash); - RemoveInternalTimer($hash); - return undef; + my ( $hash, $arg ) = @_; + DevIo_CloseDev($hash); + RemoveInternalTimer($hash); + return undef; } @@ -133,40 +135,40 @@ sub HTTPMOD_Undef($$) sub HTTPMOD_Attr(@) { - my ($cmd,$name,$aName,$aVal) = @_; + my ($cmd,$name,$aName,$aVal) = @_; # $cmd can be "del" or "set" - # $name is device name - # aName and aVal are Attribute name and value + # $name is device name + # aName and aVal are Attribute name and value - # Attributes are readingsRegexp.*, requestHeader.* and requestData.* + # 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? + # 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; + # 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; } @@ -177,20 +179,20 @@ HTTPMOD_Attr(@) ######################################################################### 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); - } + 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; + return undef; } # @@ -199,19 +201,19 @@ sub HTTPMOD_Set($@) ######################################################################### sub HTTPMOD_Get($@) { - my ( $hash, @a ) = @_; - return "\"get HTTPMOD\" needs at least an argument" if ( @a < 2 ); + 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; + # @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; } @@ -221,31 +223,40 @@ sub HTTPMOD_Get($@) ################################### sub HTTPMOD_GetUpdate($) { - my ($hash) = @_; - my $name = $hash->{NAME}; - - InternalTimer(gettimeofday()+$hash->{Interval}, "HTTPMOD_GetUpdate", $hash, 1); - Log3 $name, 4, "HTTPMOD: GetUpdate called, hash = $hash, name = $name"; - - if ( $hash->{url} eq "none" ) { - return 0; - } + my ($hash) = @_; + my $name = $hash->{NAME}; + + InternalTimer(gettimeofday()+$hash->{Interval}, "HTTPMOD_GetUpdate", $hash, 1); + + return if(AttrVal($name, "disable", undef)); - 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}; - } + Log3 $name, 4, "HTTPMOD: GetUpdate called, hash = $hash, name = $name"; + + if ( $hash->{url} eq "none" ) { + return 0; + } - 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}; - } - - HttpUtils_NonblockingGet($hash); + 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); } # @@ -254,54 +265,54 @@ sub HTTPMOD_GetUpdate($) ################################### 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 ($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; - + 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; + } @@ -315,102 +326,106 @@ sub HTTPMOD_Read($$$)

HTTPMOD

    - This module provides a generic way to retrieve information from devices with an HTTP Interface and store them in Readings. - It queries a given URL with Headers and data defined by attributes. - From the HTTP Response it extracts Readings named in attributes using Regexes also defined by attributes. -

    - Prerequisites -
      -
      -
    • - This Module uses the non blocking HTTP function HttpUtils_NonblockingGet provided by FHEM's HttpUtils in a new Version published in December 2013.
      - If not already installed in your environment, please update FHEM or install it manually using appropriate commands from your environment.
      -
    • - -
    -
    + This module provides a generic way to retrieve information from devices with an HTTP Interface and store them in Readings. + It queries a given URL with Headers and data defined by attributes. + From the HTTP Response it extracts Readings named in attributes using Regexes also defined by attributes. +

    + Prerequisites +
      +
      +
    • + This Module uses the non blocking HTTP function HttpUtils_NonblockingGet provided by FHEM's HttpUtils in a new Version published in December 2013.
      + If not already installed in your environment, please update FHEM or install it manually using appropriate commands from your environment.
      +
    • + +
    +
    - - Define -
      -
      - define <name> HTTPMOD <URL> <Interval> -

      - The module connects to the given URL every Interval seconds, sends optional headers and data and then parses the response
      -
      - Example:
      -
      -
        define PM HTTPMOD http://MyPoolManager/cgi-bin/webgui.fcgi 60
      -
    -
    + + Define +
      +
      + define <name> HTTPMOD <URL> <Interval> +

      + The module connects to the given URL every Interval seconds, sends optional headers and data and then parses the response
      +
      + Example:
      +
      +
        define PM HTTPMOD http://MyPoolManager/cgi-bin/webgui.fcgi 60
      +
    +
    - - Configuration of HTTP Devices

    -
      - Specify optional headers as attr requestHeader1 to attr requestHeaderX,
      - optional POST data as attr requestData and then
      - pairs of attr readingNameX and attr readingRegexX to define which readings you want to extract from the HTTP - response and how to extract them. -

      - Example for a PoolManager 5:

      -
        - define PM HTTPMOD http://MyPoolManager/cgi-bin/webgui.fcgi 60
        - attr PM readingsName1 PH
        - attr PM readingsName2 CL
        - attr PM readingsName3 TEMP
        - attr PM readingsRegex1 34.4001.value":[ \t]+"([\d\.]+)"
        - attr PM readingsRegex2 34.4008.value":[ \t]+"([\d\.]+)"
        - attr PM readingsRegex3 34.4033.value":[ \t]+"([\d\.]+)"
        - attr PM requestData {"get" :["34.4001.value" ,"34.4008.value" ,"34.4033.value", "14.16601.value", "14.16602.value"]}
        - attr PM requestHeader1 Content-Type: application/json
        - attr PM requestHeader2 Accept: */*
        - attr PM stateFormat {sprintf("%.1f Grad, PH %.1f, %.1f mg/l Chlor", ReadingsVal($name,"TEMP",0), ReadingsVal($name,"PH",0), ReadingsVal($name,"CL",0))}
        -
      - If you need to do some calculation on a raw value before it is used as a reading you can define the attribute readingsExprX - which can use the raw value from the variable $val -

      - Example:

      -
        - attr PM readingsExpr3 $val * 10
        -
      -
    -
    + + Configuration of HTTP Devices

    +
      + Specify optional headers as attr requestHeader1 to attr requestHeaderX,
      + optional POST data as attr requestData and then
      + pairs of attr readingNameX and attr readingRegexX to define which readings you want to extract from the HTTP + response and how to extract them. +

      + Example for a PoolManager 5:

      +
        + define PM HTTPMOD http://MyPoolManager/cgi-bin/webgui.fcgi 60
        + attr PM readingsName1 PH
        + attr PM readingsName2 CL
        + attr PM readingsName3 TEMP
        + attr PM readingsRegex1 34.4001.value":[ \t]+"([\d\.]+)"
        + attr PM readingsRegex2 34.4008.value":[ \t]+"([\d\.]+)"
        + attr PM readingsRegex3 34.4033.value":[ \t]+"([\d\.]+)"
        + attr PM requestData {"get" :["34.4001.value" ,"34.4008.value" ,"34.4033.value", "14.16601.value", "14.16602.value"]}
        + attr PM requestHeader1 Content-Type: application/json
        + attr PM requestHeader2 Accept: */*
        + attr PM stateFormat {sprintf("%.1f Grad, PH %.1f, %.1f mg/l Chlor", ReadingsVal($name,"TEMP",0), ReadingsVal($name,"PH",0), ReadingsVal($name,"CL",0))}
        +
      + If you need to do some calculation on a raw value before it is used as a reading you can define the attribute readingsExprX + which can use the raw value from the variable $val +

      + Example:

      +
        + attr PM readingsExpr3 $val * 10
        +
      +
    +
    - - Set-Commands
    -
      - none -
    -
    - - Get-Commands
    -
      - none -
    -
    - - Attributes

    -
      -
    • do_not_notify
    • -
    • readingFnAttributes
    • -
      -
    • requestHeader.*
    • - Define an additional HTTP Header to set in the HTTP request
      -
    • requestData
    • - POST Data to be sent in the request. If not defined, it will be a GET request as defined in HttpUtils used by this module
      -
    • readingsName.*
    • - the name of a reading to extract with the corresponding readingRegex
      -
    • readingsRegex.*
    • - defines the regex to be used for extracting the reading. The value to extract should be in a sub expression e.g. ([\d\.]+) in the above example
      -
    • readingsExpr.*
    • - defines an expression that is used in an eval to compute the readings value. The raw value will be in the variable $val. - -
    -
    - Author's notes

    -
      -
    • If you don't know which URLs, headers or POST data your web GUI uses, you might try a local proxy like BurpSuite to track requests and responses
    • -
    + + Set-Commands
    +
      + none +
    +
    + + Get-Commands
    +
      + none +
    +
    + + Attributes

    +
      +
    • do_not_notify
    • +
    • readingFnAttributes
    • +
      +
    • requestHeader.*
    • + Define an additional HTTP Header to set in the HTTP request
      +
    • requestData
    • + POST Data to be sent in the request. If not defined, it will be a GET request as defined in HttpUtils used by this module
      +
    • readingsName.*
    • + the name of a reading to extract with the corresponding readingRegex
      +
    • readingsRegex.*
    • + defines the regex to be used for extracting the reading. The value to extract should be in a sub expression e.g. ([\d\.]+) in the above example
      +
    • readingsExpr.*
    • + defines an expression that is used in an eval to compute the readings value. The raw value will be in the variable $val. +
    • noShutdown
    • + pass the noshutdown flag to HTTPUtils for webservers that need it (some embedded webservers only deliver empty pages otherwise) +
    • disable
    • + stop doing HTTP requests while this attribute is set to 1 + +
    +
    + Author's notes

    +
      +
    • If you don't know which URLs, headers or POST data your web GUI uses, you might try a local proxy like BurpSuite to track requests and responses
    • +
=end html