From f3976fc03e8b96da656fd50fac896f4446d4348c Mon Sep 17 00:00:00 2001 From: loredo Date: Wed, 8 Jan 2014 23:53:52 +0000 Subject: [PATCH] add new module 98_GEOFANCY.pm git-svn-id: svn://svn.code.sf.net/p/fhem/code/trunk@4593 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/98_GEOFANCY.pm | 373 +++++++++++++++++++++++++++++++++++++++ fhem/MAINTAINER.txt | 1 + 3 files changed, 375 insertions(+) create mode 100644 fhem/FHEM/98_GEOFANCY.pm diff --git a/fhem/CHANGED b/fhem/CHANGED index 100d94dbb..3867ad606 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,6 +1,7 @@ # 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. - SVN + - feature: new module 98_GEOFANCY.pm added (loredo) - feature: new module 70_XBMC.pm added (dbokermann) - feature: new module 51_RPI_GPIO.pm added (klausw) - bugfix: Dashboard: fixed bug identification an existing Weblink. diff --git a/fhem/FHEM/98_GEOFANCY.pm b/fhem/FHEM/98_GEOFANCY.pm new file mode 100644 index 000000000..109d89458 --- /dev/null +++ b/fhem/FHEM/98_GEOFANCY.pm @@ -0,0 +1,373 @@ +# $Id$ +############################################################################## +# +# 98_GEOFANCY.pm +# An FHEM Perl module to receive geofencing webhooks from geofancy.com. +# +# Copyright by Julian Pawlowski +# e-mail: julian.pawlowski at gmail.com +# +# Based on HTTPSRV from Dr. Boris Neubert +# +# 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 . +# +# +# Version: 1.0.0 +# +# Major Version History: +# - 1.0.0 - 2014-01-09 +# -- First release +# +############################################################################## + +package main; + +use strict; +use warnings; +use vars qw(%data); +use HttpUtils; +use Data::Dumper; + +sub GEOFANCY_Set($@); +sub GEOFANCY_Define($$); +sub GEOFANCY_Undefine($$); + +######################### +sub GEOFANCY_addExtension($$$) { + my ( $name, $func, $link ) = @_; + + my $url = "/$link"; + Log3 $name, 3, "Registering GEOFANCY $name for URL $url..."; + $data{FWEXT}{$url}{deviceName} = $name; + $data{FWEXT}{$url}{FUNC} = $func; + $data{FWEXT}{$url}{LINK} = $link; +} + +sub GEOFANCY_removeExtension($) { + my ($link) = @_; + + my $url = "/$link"; + my $name = $data{FWEXT}{$url}{deviceName}; + Log3 $name, 3, "Unregistering GEOFANCY $name for URL $url..."; + delete $data{FWEXT}{$url}; +} + +################################### +sub GEOFANCY_Initialize($) { + my ($hash) = @_; + + Log3 $hash, 5, "GEOFANCY_Initialize: Entering"; + + $hash->{SetFn} = "GEOFANCY_Set"; + $hash->{DefFn} = "GEOFANCY_Define"; + $hash->{UndefFn} = "GEOFANCY_Undefine"; + $hash->{AttrList} = "devAlias " . $readingFnAttributes; +} + +################################### +sub GEOFANCY_Define($$) { + + my ( $hash, $def ) = @_; + + my @a = split( "[ \t]+", $def, 5 ); + + return "Usage: define GEOFANCY " + if ( int(@a) != 3 ); + my $name = $a[0]; + my $infix = $a[2]; + + $hash->{fhem}{infix} = $infix; + + GEOFANCY_addExtension( $name, "GEOFANCY_CGI", $infix ); + + readingsBeginUpdate($hash); + readingsBulkUpdate( $hash, "state", "initialized" ); + readingsEndUpdate( $hash, 1 ); + return undef; +} + +################################### +sub GEOFANCY_Undefine($$) { + + my ( $hash, $name ) = @_; + + GEOFANCY_removeExtension( $hash->{fhem}{infix} ); + + return undef; +} + +################################### +sub GEOFANCY_Set($@) { + my ( $hash, @a ) = @_; + my $name = $hash->{NAME}; + my $state = $hash->{STATE}; + + Log3 $name, 5, "GEOFANCY $name: called function GEOFANCY_Set()"; + + return "No Argument given" if ( !defined( $a[1] ) ); + + my $usage = "Unknown argument " . $a[1] . ", choose one of clear:readings"; + + # clear + if ( $a[1] eq "clear" ) { + Log3 $name, 2, "GEOFANCY set $name " . $a[1]; + + if ( $a[2] ) { + + # readings + if ( $a[2] eq "readings" ) { + delete $hash->{READINGS}; + readingsBeginUpdate($hash); + readingsBulkUpdate( $hash, "state", "clearedReadings" ); + readingsEndUpdate( $hash, 1 ); + } + + } + + else { + return "No Argument given, choose one of readings "; + } + } + + # return usage hint + else { + return $usage; + } + + return undef; +} + +############################################################################################################ +# +# Begin of helper functions +# +############################################################################################################ + +################################### +sub GEOFANCY_CGI() { + + # /$infix?device=UUID&id=UUID&latitude=xx.x&longitude=xx.x&trigger=(enter|exit) + my ($request) = @_; + + my $hash; + my $name; + my $link; + my $URI; + my $device; + my $id; + my $lat; + my $long; + my $trigger; + my $msg; + + # data via GET + if ( $request =~ m,^(/[^/]+)\?((.*)?)?$, ) { + $link = $1; + $URI = $2; + + # get device name + $name = $data{FWEXT}{$link}{deviceName} if ( $data{FWEXT}{$link} ); + + # return error if no such device + return ( "text/plain; charset=utf-8", + "NOK No GEOFANCY device for webhook $link" ) + unless ($name); + + # extract values from URI + my $webArgs; + foreach my $pv ( split( "&", $URI ) ) { + next if ( $pv eq "" ); + $pv =~ s/\+/ /g; + $pv =~ s/%([\dA-F][\dA-F])/chr(hex($1))/ige; + my ( $p, $v ) = split( "=", $pv, 2 ); + + $webArgs->{$p} = $v; + } + + if ( !defined( $webArgs->{device} ) + || !defined( $webArgs->{id} ) + || !defined( $webArgs->{latitude} ) + || !defined( $webArgs->{longitude} ) + || !defined( $webArgs->{trigger} ) + || $webArgs->{device} eq "" + || $webArgs->{id} eq "" + || $webArgs->{latitude} eq "" + || $webArgs->{longitude} eq "" + || $webArgs->{trigger} eq "" ) + { + $msg = "device="; + $msg .= $webArgs->{device} if ( $webArgs->{device} ); + $msg .= " id="; + $msg .= $webArgs->{id} if ( $webArgs->{id} ); + $msg .= " latitude="; + $msg .= $webArgs->{latitude} if ( $webArgs->{latitude} ); + $msg .= " longitude="; + $msg .= $webArgs->{longitude} if ( $webArgs->{longitude} ); + $msg .= " trigger="; + $msg .= $webArgs->{trigger} if ( $webArgs->{trigger} ); + + Log3 $name, 3, + "GEOFANCY: Insufficient data received for webhook $link:\n" + . $msg; + + return ( "text/plain; charset=utf-8", + "NOK\nInsufficient data received for webhook $link:\n" . $msg ); + } + + $device = $webArgs->{device}; + $id = $webArgs->{id}; + $lat = $webArgs->{latitude}; + $long = $webArgs->{longitude}; + $trigger = $webArgs->{trigger}; + } + + # data via POST + else { + Log3 undef, 3, +"GEOFANCY: Data transfer via POST not implemented. (request URI: $request)"; + + return ( + "text/plain; charset=utf-8", +"NOK Data transfer via POST not implemented. (request URI: $request)" + ); + } + + # return error if unknown trigger + return ( "text/plain; charset=utf-8", "$trigger NOK" ) + if ( $trigger ne "enter" && $trigger ne "exit" && $trigger ne "test" ); + + $hash = $defs{$name}; + + # Device alias handling + # + delete $hash->{helper}{device_aliases} + if $hash->{helper}{device_aliases}; + delete $hash->{helper}{device_names} + if $hash->{helper}{device_names}; + + if ( defined( $attr{$name}{devAlias} ) ) { + my @devices = split( ' ', $attr{$name}{devAlias} ); + + if (@devices) { + foreach (@devices) { + my @device = split( ':', $_ ); + $hash->{helper}{device_aliases}{ $device[0] } = + $device[1]; + $hash->{helper}{device_names}{ $device[1] } = + $device[0]; + } + } + } + + $device = $hash->{helper}{device_aliases}{$device} + if $hash->{helper}{device_aliases}{$device}; + + Log3 $name, 4, + "GEOFANCY $name: " + . $device . ": id=" + . $id + . " latitude=" + . $lat + . " longitude=" + . $long + . " trigger=" + . $trigger; + + readingsBeginUpdate($hash); + readingsBulkUpdate( $hash, $device, $trigger . " " . $id ); + readingsBulkUpdate( $hash, "lastDevice", $device ); + readingsBulkUpdate( $hash, "lastEnter", $device . " " . $id ) + if $trigger eq "enter"; + readingsBulkUpdate( $hash, "lastExit", $device . " " . $id ) + if $trigger eq "exit"; + readingsBulkUpdate( $hash, "lastId_" . $device, $id ); + readingsBulkUpdate( $hash, "lastLat_" . $device, $lat ); + readingsBulkUpdate( $hash, "lastLong_" . $device, $long ); + readingsBulkUpdate( $hash, "lastTrigger_" . $device, $trigger ); + readingsBulkUpdate( $hash, "state", + "dev:$device trig:$trigger id:$id lat:$lat long:$long" ); + readingsEndUpdate( $hash, 1 ); + + $msg = "$trigger OK"; + $msg .= "\ndevice=$device id=$id lat=$lat long=$long trigger=$trigger" + if ( $trigger eq "test" ); + + return ( "text/plain; charset=utf-8", $msg ); +} + +1; + +=pod +=begin html + + +

GEOFANCY

+
    + Provides webhook receiver for geofencing from geofancy.com.

    + + GEOFANCY is an extension to FHEMWEB. You need to install FHEMWEB to use GEOFANCY.

    + + + Define +
      + define <name> <infix>

      + + Defines the webhook server. <infix> is the portion behind the FHEMWEB base URL (usually + http://hostname:8083/fhem) + + Example: +
        + define geofancy GEOFANCY geo
        +
      + The webhook will be reachable at http://hostname:8083/fhem/geo in that case.
      +
      +
    + + + Set +
      +
    • clear   readings   can be used to cleanup auto-created readings from deprecated devices.
    • +
    +

    + + + Attributes +

    +
      +
    • devAlias: can be used to rename device names in the format DEVICEUUID:Aliasname. Separate using blank to rename multiple devices.
    • +
    +

    + + Usage information +

    +
      + Likely your FHEM installation is not reachable directly from the internet (good idea!). + It is recommended to have a reverse proxy like nginx or Apache in front of FHEM where you can make sure access is only possible + to specific subdirectories like /fhem/geo. + You might also want to think about protecting the access by using HTTP Basic Authentication and encryption via SSL. + Also the definition of a dedicated FHEMWEB instance for that purpose might help to restrict FHEM's functionality + (note that the 'hidden' attributes of FHEMWEB currently do NOT protect from just guessing/knowing the correct URL!) +
      + To make that reverse proxy available from the internet, just forward the appropriate port via your internet router. +
      + The actual solution on how you can securely make your Geofancy webhook available to the internet is not part of this documentation + and depends on your own skills. +
    +

    +
+ +=end html +=cut diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index a26bf7469..7e4a4fe19 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -171,6 +171,7 @@ FHEM/95_holiday.pm rudolfkoenig http://forum.fhem.de Sonstiges FHEM/95_remotecontrol.pm ulimaass http://forum.fhem.de Frontends FHEM/98_apptime.pm martinp876 http://forum.fhem.de Sonstiges FHEM/98_CULflash.pm rudolfkoenig http://forum.fhem.de Sonstiges +FHEM/98_GEOFANCY.pm loredo http://forum.fhem.de Unterstuetzende Dienste FHEM/98_HMinfo.pm martinp876 http://forum.fhem.de HomeMatic FHEM/98_Heating_Control.pm dietmar63 http://forum.fhem.de Unterstuetzende Dienste FHEM/98_JsonList.pm mfr69bs http://forum.fhem.de Automatisierung