diff --git a/fhem/CHANGED b/fhem/CHANGED index 98259831c..9a0247f78 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +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. + - update: 73_NUKIBridge,74_NUKIDevice: New Version 0.4.0 + More Feature get,set ,Callback/Webhook Funktion - change: 98_exportdevice: command was deprecated and moved to contrib in favor of FHEMWEB builtin functions - bugfix: 93_DbRep: bugfix group by-clause due to incompatible changes made diff --git a/fhem/FHEM/73_NUKIBridge.pm b/fhem/FHEM/73_NUKIBridge.pm index 59784a6dc..2d65ccd45 100644 --- a/fhem/FHEM/73_NUKIBridge.pm +++ b/fhem/FHEM/73_NUKIBridge.pm @@ -25,17 +25,28 @@ # ############################################################################### +################################# +######### Wichtige Hinweise und Links ################# + +## Beispiel für Logausgabe +# https://forum.fhem.de/index.php/topic,55756.msg508412.html#msg508412 + +## +# + + +################################ + package main; use strict; use warnings; use JSON; -#use Time::HiRes qw(gettimeofday); + use HttpUtils; -my $version = "0.2.1"; -my $bridgeAPI = "1.0.2"; +my $version = "0.4.0"; @@ -57,19 +68,20 @@ sub NUKIBridge_Initialize($) { $hash->{WriteFn} = "NUKIBridge_Read"; $hash->{Clients} = ":NUKIDevice:"; + # Consumer $hash->{SetFn} = "NUKIBridge_Set"; - $hash->{DefFn} = "NUKIBridge_Define"; - $hash->{UndefFn} = "NUKIBridge_Undef"; - $hash->{AttrFn} = "NUKIBridge_Attr"; - $hash->{AttrList} = "interval ". - "disable:1 ". + $hash->{GetFn} = "NUKIBridge_Get"; + $hash->{DefFn} = "NUKIBridge_Define"; + $hash->{UndefFn} = "NUKIBridge_Undef"; + $hash->{AttrFn} = "NUKIBridge_Attr"; + $hash->{AttrList} = "disable:1 ". $readingFnAttributes; foreach my $d(sort keys %{$modules{NUKIBridge}{defptr}}) { - my $hash = $modules{NUKIBridge}{defptr}{$d}; - $hash->{VERSION} = $version; + my $hash = $modules{NUKIBridge}{defptr}{$d}; + $hash->{VERSION} = $version; } } @@ -91,17 +103,16 @@ sub NUKIBridge_Define($$) { - my $name = $a[0]; - my $host = $a[2]; + my $name = $a[0]; + my $host = $a[2]; my $token = $a[3]; - my $port = 8080; - my $interval = 60; + my $port = 8080; - $hash->{HOST} = $host; - $hash->{PORT} = $port; - $hash->{TOKEN} = $token; - $hash->{INTERVAL} = $interval; - $hash->{VERSION} = $version; + $hash->{HOST} = $host; + $hash->{PORT} = $port; + $hash->{TOKEN} = $token; + $hash->{VERSION} = $version; + $hash->{helper}{aliveCount} = 0; @@ -113,10 +124,9 @@ sub NUKIBridge_Define($$) { RemoveInternalTimer($hash); if( $init_done ) { - NUKIBridge_Get($hash) if( ($hash->{HOST}) and ($hash->{TOKEN}) ); - #NUKIBridge_GetCheckBridgeAlive($hash) if( ($hash->{HOST}) and ($hash->{TOKEN}) ); + NUKIBridge_firstRun($hash) if( ($hash->{HOST}) and ($hash->{TOKEN}) ); } else { - InternalTimer( gettimeofday()+15, "NUKIBridge_Get", $hash, 0 ) if( ($hash->{HOST}) and ($hash->{TOKEN}) ); + InternalTimer( gettimeofday()+15, "NUKIBridge_firstRun", $hash, 0 ) if( ($hash->{HOST}) and ($hash->{TOKEN}) ); } $modules{NUKIBridge}{defptr}{$hash->{HOST}} = $hash; @@ -145,49 +155,31 @@ sub NUKIBridge_Attr(@) { my $orig = $attrVal; + if( $attrName eq "disable" ) { - if( $cmd eq "set" ) { - if( $attrVal eq "0" ) { - RemoveInternalTimer( $hash ); - InternalTimer( gettimeofday()+2, "NUKIBridge_GetCheckBridgeAlive", $hash, 0 ); - readingsSingleUpdate($hash, 'state', 'Initialized', 1 ); - Log3 $name, 3, "NUKIBridge ($name) - enabled"; - } else { - RemoveInternalTimer( $hash ); - readingsSingleUpdate($hash, 'state', 'disabled', 1 ); - Log3 $name, 3, "NUKIBridge ($name) - disabled"; - } - - } else { - RemoveInternalTimer( $hash ); - InternalTimer( gettimeofday()+2, "NUKIBridge_GetCheckBridgeAlive", $hash, 0 ); - readingsSingleUpdate($hash, 'state', 'Initialized', 1 ); - Log3 $name, 3, "NUKIBridge ($name) - enabled"; + if( $cmd eq "set" and $attrVal eq "1" ) { + readingsSingleUpdate ( $hash, "state", "disabled", 1 ); + Log3 $name, 3, "NUKIBridge ($name) - disabled"; + } + + elsif( $cmd eq "del" ) { + readingsSingleUpdate ( $hash, "state", "active", 1 ); + Log3 $name, 3, "NUKIBridge ($name) - enabled"; } } - if( $attrName eq "interval" ) { - if( $cmd eq "set" ) { - if( $attrVal < 30 ) { - Log3 $name, 3, "NUKIBridge ($name) - interval too small, please use something > 30 (sec), default is 60 (sec)"; - return "interval too small, please use something > 30 (sec), default is 60 (sec)"; - } else { - $hash->{INTERVAL} = $attrVal; - Log3 $name, 3, "NUKIBridge ($name) - set interval to $attrVal"; - } - } - elsif( $cmd eq "del" ) { - $hash->{INTERVAL} = 60; - Log3 $name, 3, "NUKIBridge ($name) - set interval to default"; - - } else { - if( $cmd eq "set" ) { - $attr{$name}{$attrName} = $attrVal; - Log3 $name, 3, "NUKIBridge ($name) - $attrName : $attrVal"; - } - } + if( $attrName eq "disabledForIntervals" ) { + if( $cmd eq "set" ) { + Log3 $name, 3, "NUKIBridge ($name) - enable disabledForIntervals"; + readingsSingleUpdate ( $hash, "state", "Unknown", 1 ); + } + + elsif( $cmd eq "del" ) { + readingsSingleUpdate ( $hash, "state", "active", 1 ); + Log3 $name, 3, "NUKIBridge ($name) - delete disabledForIntervals"; + } } - + return undef; } @@ -200,38 +192,74 @@ sub NUKIBridge_Set($@) { if($cmd eq 'autocreate') { return "usage: autocreate" if( @args != 0 ); - NUKIBridge_Get($hash); + NUKIBridge_Call($hash,$hash,"list",undef,undef) if( !IsDisabled($name) ); return undef; - } elsif($cmd eq 'statusRequest') { + } elsif($cmd eq 'info') { + return "usage: statusRequest" if( @args != 0 ); - NUKIBridge_GetCheckBridgeAlive($hash); + NUKIBridge_Call($hash,$hash,"info",undef,undef) if( !IsDisabled($name) ); return undef; - } elsif($cmd eq 'other2') { + } elsif($cmd eq 'fwUpdate') { + return "usage: fwUpdate" if( @args != 0 ); + + NUKIBridge_CallBlocking($hash,"fwupdate",undef) if( !IsDisabled($name) ); + + return undef; + + } elsif($cmd eq 'reboot') { + return "usage: reboot" if( @args != 0 ); + + NUKIBridge_CallBlocking($hash,"reboot",undef) if( !IsDisabled($name) ); + + return undef; + + } elsif($cmd eq 'clearLog') { + return "usage: clearLog" if( @args != 0 ); + + NUKIBridge_CallBlocking($hash,"clearlog",undef) if( !IsDisabled($name) ); + + } elsif($cmd eq 'callbackRemove') { + return "usage: callbackRemove" if( @args != 1 ); + my $id = "id=" . join( " ", @args ); + + my $resp = NUKIBridge_CallBlocking($hash,"callback/remove",$id) if( !IsDisabled($name) ); + if( $resp->{success} eq "true" and !IsDisabled($name) ) { + return "Success Callback $id removed"; + } else { + return "remove Callback failed"; + } } else { - my $list = "statusRequest:noArg autocreate:noArg"; + my $list = "info:noArg autocreate:noArg clearLog:noArg fwUpdate:noArg reboot:noArg callbackRemove:0,1,2"; return "Unknown argument $cmd, choose one of $list"; } } -sub NUKIBridge_Get($) { +sub NUKIBridge_Get($@) { - my ($hash) = @_; - my $name = $hash->{NAME}; + my ($hash, $name, $cmd, @args) = @_; + my ($arg, @params) = @args; - RemoveInternalTimer($hash); - - NUKIBridge_Call($hash,$hash,"list",undef,undef) if( !IsDisabled($name) ); - NUKIBridge_GetCheckBridgeAlive($hash); - - Log3 $name, 4, "NUKIBridge ($name) - Call NUKIBridge_Get" if( !IsDisabled($name) ); + if($cmd eq 'logFile') { + return "usage: logFile" if( @args != 0 ); + + NUKIBridge_getLogfile($hash) if( !IsDisabled($name) ); + + } elsif($cmd eq 'callbackList') { + return "usage: callbackList" if( @args != 0 ); + + NUKIBridge_getCallbackList($hash) if( !IsDisabled($name) ); + + } else { + my $list = "logFile:noArg callbackList:noArg"; + return "Unknown argument $cmd, choose one of $list"; + } - return 1; } sub NUKIBridge_GetCheckBridgeAlive($) { @@ -240,96 +268,105 @@ sub NUKIBridge_GetCheckBridgeAlive($) { my $name = $hash->{NAME}; RemoveInternalTimer($hash); + Log3 $name, 4, "NUKIBridge ($name) - NUKIBridge_GetCheckBridgeAlive"; if( !IsDisabled($name) ) { - NUKIBridge_Call($hash,$hash,"list",undef,undef,1); + NUKIBridge_Call($hash,$hash,"alive",undef,undef); - InternalTimer( gettimeofday()+$hash->{INTERVAL}, "NUKIBridge_GetCheckBridgeAlive", $hash, 1 ); + InternalTimer( gettimeofday()+17+int(rand(15)), "NUKIBridge_GetCheckBridgeAlive", $hash, 1 ); Log3 $name, 4, "NUKIBridge ($name) - Call InternalTimer for NUKIBridge_GetCheckBridgeAlive"; } - - return 1; } -sub NUKIBridge_Call($$$$$;$) { +sub NUKIBridge_firstRun($) { - my ($hash,$chash,$path,$lockAction,$nukiId,$alive) = @_; + my ($hash) = @_; + my $name = $hash->{NAME}; + + + RemoveInternalTimer($hash); + NUKIBridge_Call($hash,$hash,"list",undef,undef) if( !IsDisabled($name) ); + NUKIBridge_GetCheckBridgeAlive($hash); + + return undef; +} + +sub NUKIBridge_Call($$$$$) { + + my ($hash,$chash,$path,$lockAction,$nukiId) = @_; my $name = $hash->{NAME}; my $host = $hash->{HOST}; my $port = $hash->{PORT}; my $token = $hash->{TOKEN}; - $alive = 0 if( !defined($alive) ); - my $uri = "http://" . $hash->{HOST} . ":" . $port; $uri .= "/" . $path if( defined $path); $uri .= "?token=" . $token if( defined($token) ); - $uri .= "&action=" . $lockActions{$lockAction} if( defined($lockAction) ); + $uri .= "&action=" . $lockActions{$lockAction} if( defined($lockAction) and $path ne "callback/add" ); + $uri .= "&url=" . $lockAction if( defined($lockAction) and $path eq "callback/add" ); $uri .= "&nukiId=" . $nukiId if( defined($nukiId) ); HttpUtils_NonblockingGet( - { - url => $uri, - timeout => 10, - hash => $hash, - chash => $chash, - endpoint => $path, - alive => $alive, - method => "GET", - doTrigger => 1, - noshutdown => 1, - callback => \&NUKIBridge_Dispatch, - } + { + url => $uri, + timeout => 30, + hash => $hash, + chash => $chash, + endpoint => $path, + header => "Accept: application/json", + method => "GET", + callback => \&NUKIBridge_Distribution, + } ); Log3 $name, 4, "NUKIBridge ($name) - Send HTTP POST with URL $uri"; - - #return undef; # beim Aufruf aus dem logischen Modul kam immer erst ein Fehler, deshalb auskommentiert } -sub NUKIBridge_Dispatch($$$) { +sub NUKIBridge_Distribution($$$) { my ( $param, $err, $json ) = @_; - my $hash = $param->{hash}; - my $doTrigger = $param->{doTrigger}; - my $name = $hash->{NAME}; - my $host = $hash->{HOST}; + my $hash = $param->{hash}; + my $doTrigger = $param->{doTrigger}; + my $name = $hash->{NAME}; + my $host = $hash->{HOST}; + - readingsBeginUpdate($hash); if( defined( $err ) ) { - - if( $err ne "" and $param->{endpoint} eq "list" and $param->{alive} eq 1 ) { + if ( $err ne "" ) { + if ($param->{endpoint} eq "alive") { + readingsBulkUpdate( $hash, "state", "not connected") if( $hash->{helper}{aliveCount} > 1 ); + $hash->{helper}{aliveCount} = $hash->{helper}{aliveCount} + 1; + } - readingsBulkUpdate( $hash, "state", "not connected"); - readingsBulkUpdate( $hash, "lastError", $err ); - - Log3 $name, 4, "NUKIBridge ($name) - Bridge ist offline"; - readingsEndUpdate( $hash, 1 ); - return; - } - - elsif ( $err ne "" ) { - - readingsBulkUpdate( $hash, "lastError", $err ); + readingsBulkUpdate( $hash, "lastError", $err ) if( ReadingsVal($name,"state","not connected") eq "not connected" ); Log3 $name, 4, "NUKIBridge ($name) - error while requesting: $err"; readingsEndUpdate( $hash, 1 ); return $err; - } + } } if( $json eq "" and exists( $param->{code} ) && $param->{code} ne 200 ) { + if( $param->{endpoint} eq "alive") { + readingsBulkUpdate( $hash, "state", "connected" ); + Log3 $name, 5, "NUKIBridge ($name) - Bridge ist online"; + + readingsEndUpdate( $hash, 1 ); + $hash->{helper}{aliveCount} = 0; + return; + } + readingsBulkUpdate( $hash, "lastError", "Internal error, " .$param->{code} ); - Log3 $name, 4, "NUKIBridge ($name) - received http code " .$param->{code}." without any data after requesting"; + Log3 $name, 4, "NUKIBridge ($name) - received http code " .$param->{code}." without any data after requesting"; - readingsEndUpdate( $hash, 1 ); - return "received http code ".$param->{code}." without any data after requesting"; + readingsEndUpdate( $hash, 1 ); + return "received http code ".$param->{code}." without any data after requesting"; } if( ( $json =~ /Error/i ) and exists( $param->{code} ) ) { @@ -343,13 +380,13 @@ sub NUKIBridge_Dispatch($$$) { NUKIDevice_Parse($param->{chash},$param->{code}) if( $param->{code} eq 400 and $hash != $param->{chash} ); - Log3 $name, 4, "NUKIBridge ($name) - invalid API token" if( $param->{code} eq 401 ); - Log3 $name, 4, "NUKIBridge ($name) - nukiId is not known" if( $param->{code} eq 404 ); - Log3 $name, 4, "NUKIBridge ($name) - action is undefined" if( $param->{code} eq 400 and $hash == $param->{chash} ); - - - ######### Zum testen da ich kein Nuki Smartlock habe ############ - #if ( $param->{code} eq 404 ) { + Log3 $name, 4, "NUKIBridge ($name) - invalid API token" if( $param->{code} eq 401 ); + Log3 $name, 4, "NUKIBridge ($name) - nukiId is not known" if( $param->{code} eq 404 ); + Log3 $name, 4, "NUKIBridge ($name) - action is undefined" if( $param->{code} eq 400 and $hash == $param->{chash} ); + + + ######### Zum testen da ich kein Nuki Smartlock habe ############ + #if ( $param->{code} eq 404 ) { # if( defined($param->{chash}->{helper}{lockAction}) ) { # Log3 $name, 3, "NUKIBridge ($name) - Test JSON String for lockAction"; # $json = '{"success": true, "batteryCritical": false}'; @@ -362,24 +399,14 @@ sub NUKIBridge_Dispatch($$$) { readingsEndUpdate( $hash, 1 ); - return $param->{code}; + return $param->{code}; } - if( $param->{code} eq 200 and $param->{endpoint} eq "list" and $param->{alive} eq 1 ) { - - readingsBulkUpdate( $hash, "state", "connected" ); - Log3 $name, 5, "NUKIBridge ($name) - Bridge ist online"; - - readingsEndUpdate( $hash, 1 ); - return; - } - - if( $hash == $param->{chash} ) { - #$json = '[{"nukiId": 1, "name": "Home"}, {"nukiId": 2, "name": "Grandma"}]'; # zum testen da ich kein Nuki Smartlock habe + #$json = '[{"nukiId": 1,"name": "Home","lastKnownState": {"state": 1,"stateName": "locked","batteryCritical": false,"timestamp": "2016-10-03T06:49:00+00:00"}},{"nukiId": 2,"name": "Grandma","lastKnownState": {"state": 3,"stateName": "unlocked","batteryCritical": false,"timestamp": "2016-10-03T06:49:00+00:00"}}]' if( $param->{endpoint} eq "list" ); # zum testen da ich kein Nuki Smartlock habe - NUKIBridge_ResponseProcessing($hash,$json); + NUKIBridge_ResponseProcessing($hash,$json,$param->{endpoint}); } else { @@ -390,21 +417,27 @@ sub NUKIBridge_Dispatch($$$) { return undef; } -sub NUKIBridge_ResponseProcessing($$) { +sub NUKIBridge_ResponseProcessing($$$) { - my ( $hash, $json ) = @_; + my ($hash,$json,$path) = @_; my $name = $hash->{NAME}; my $decode_json; $decode_json = decode_json($json); - if( ref($decode_json) eq "ARRAY" and scalar(@{$decode_json}) > 0 ) { + if( ref($decode_json) eq "ARRAY" and scalar(@{$decode_json}) > 0 and $path eq "list" ) { NUKIBridge_Autocreate($hash,$decode_json); + NUKIBridge_Call($hash,$hash,"info",undef,undef) if( !IsDisabled($name) ); + } + + elsif( $path eq "info" ) { + NUKIBridge_InfoProcessing($hash,$decode_json); } else { - return $json; + Log3 $name, 5, "NUKIDevice ($name) - Rückgabe Path nicht korrekt: $json"; + return; } return undef; @@ -437,17 +470,17 @@ sub NUKIBridge_Autocreate($$;$) { my $code = $name ."-".$nukiId; if( defined($modules{NUKIDevice}{defptr}{$code}) ) { - Log3 $name, 5, "$name: NukiId '$nukiId' already defined as '$modules{NUKIDevice}{defptr}{$code}->{NAME}'"; + Log3 $name, 3, "NUKIDevice ($name) - NukiId '$nukiId' already defined as '$modules{NUKIDevice}{defptr}{$code}->{NAME}'"; next; } my $devname = "NUKIDevice" . $nukiId; my $define= "$devname NUKIDevice $nukiId IODev=$name"; - Log3 $name, 5, "$name: create new device '$devname' for address '$nukiId'"; + Log3 $name, 3, "NUKIDevice ($name) - create new device '$devname' for address '$nukiId'"; my $cmdret= CommandDefine(undef,$define); if($cmdret) { - Log3 $name, 1, "($name) Autocreate: An error occurred while creating device for nukiId '$nukiId': $cmdret"; + Log3 $name, 3, "NUKIDevice ($name) - Autocreate: An error occurred while creating device for nukiId '$nukiId': $cmdret"; } else { $cmdret= CommandAttr(undef,"$devname alias $nukiName"); $cmdret= CommandAttr(undef,"$devname room NUKI"); @@ -464,18 +497,179 @@ sub NUKIBridge_Autocreate($$;$) { readingsBulkUpdate( $hash, "smartlockCount", $autocreated ); } - readingsBulkUpdate( $hash, "bridgeAPI", $bridgeAPI ); readingsEndUpdate( $hash, 1 ); if( $autocreated ) { - Log3 $name, 2, "$name: autocreated $autocreated devices"; + Log3 $name, 2, "NUKIDevice ($name) - autocreated $autocreated devices"; CommandSave(undef,undef) if( AttrVal( "autocreate", "autosave", 1 ) ); } return "created $autocreated devices"; } +sub NUKIBridge_InfoProcessing($$) { + + my ($hash,$decode_json) = @_; + my $name = $hash->{NAME}; + + my %bridgeType = ( + '1' => 'Hardware', + '2' => 'Software' + ); + + + readingsBeginUpdate($hash); + readingsBulkUpdate($hash,"appVersion",$decode_json->{versions}->{appVersion}); + readingsBulkUpdate($hash,"firmwareVersion",$decode_json->{versions}->{firmwareVersion}); + readingsBulkUpdate($hash,"wifiFirmwareVersion",$decode_json->{versions}->{wifiFirmwareVersion}); + readingsBulkUpdate($hash,"bridgeType",$bridgeType{$decode_json->{bridgeType}}); + readingsBulkUpdate($hash,"hardwareId",$decode_json->{ids}{hardwareId}); + readingsBulkUpdate($hash,"serverId",$decode_json->{ids}{serverId}); + readingsBulkUpdate($hash,"uptime",$decode_json->{uptime}); + readingsBulkUpdate($hash,"currentTime",$decode_json->{currentTime}); + readingsBulkUpdate($hash,"serverConnected",$decode_json->{serverConnected}); + readingsEndUpdate($hash,1); +} + +sub NUKIBridge_getLogfile($) { + + my ($hash) = @_; + my $name = $hash->{NAME}; + + + my $decode_json = NUKIBridge_CallBlocking($hash,"log",undef); + + Log3 $name, 4, "NUKIBridge ($name) - Log Daten werden geholt und aufbereitet"; + + + if( ref($decode_json) eq "ARRAY" and scalar(@{$decode_json}) > 0 ) { + Log3 $name, 4, "NUKIBridge ($name) - Tabelle mit Logdaten wird aufgebaut"; + + my $ret = ''; + $ret .= '
'; + $ret .= ''; + + foreach my $logs (@{$decode_json}) { + $ret .= ''; + + if($logs->{timestamp}) { + $ret .= ""; + $ret .= ""; + $ret .= ''; + } + + if($logs->{type}) { + $ret .= ""; + $ret .= ""; + $ret .= ''; + } + + foreach my $d (reverse sort keys %{$logs}) { + next if( $d eq "type" ); + next if( $d eq "timestamp" ); + + $ret .= ""; + $ret .= ""; + $ret .= ''; + } + + $ret .= ''; + } + + $ret .= '
timestamp: $logs->{timestamp} type: $logs->{type} $d: $logs->{$d}
'; + + return $ret; + } +} + +sub NUKIBridge_getCallbackList($) { + + my ($hash) = @_; + my $name = $hash->{NAME}; + + + my $decode_json = NUKIBridge_CallBlocking($hash,"callback/list",undef); + + Log3 $name, 4, "NUKIBridge ($name) - Callback Daten werden geholt und aufbereitet"; + + if( ref($decode_json->{callbacks}) eq "ARRAY" and scalar(@{$decode_json->{callbacks}}) > 0 ) { + Log3 $name, 4, "NUKIBridge ($name) - Tabelle mit Logdaten wird aufgebaut"; + + my $ret = ''; + $ret .= '
'; + + $ret .= ''; + + $ret .= ''; + $ret .= ""; + $ret .= ""; + $ret .= ""; + $ret .= ''; + + foreach my $cb (@{$decode_json->{callbacks}}) { + + $ret .= ""; + $ret .= ""; + $ret .= ""; + $ret .= ''; + } + + $ret .= '
Callback-ID Callback-URL
$cb->{id} $cb->{url}
'; + + return $ret; + } + + return "Keine Callback Daten vorhanden oder Fehler bei der Verarbeitung"; +} + +sub NUKIBridge_CallBlocking($$$) { + + my ($hash,$path,$obj) = @_; + my $name = $hash->{NAME}; + my $host = $hash->{HOST}; + my $port = $hash->{PORT}; + my $token = $hash->{TOKEN}; + + + my $url = "http://" . $hash->{HOST} . ":" . $port; + $url .= "/" . $path if( defined $path); + $url .= "?token=" . $token if( defined($token) ); + $url .= "&" . $obj if( defined($obj) ); + + + my($err,$data) = HttpUtils_BlockingGet({ + url => $url, + timeout => 3, + method => "GET", + header => "Content-Type: application/json", + }); + + + if( !$data ) { + Log3 $name, 3, "NUKIDevice ($name) - empty answer received for $url"; + return undef; + } elsif( $data =~ m'HTTP/1.1 200 OK' ) { + Log3 $name, 4, "NUKIDevice ($name) - empty answer received for $url"; + return undef; + } elsif( $data !~ m/^[\[{].*[}\]]$/ and $path ne "log" ) { + Log3 $name, 3, "NUKIDevice ($name) - invalid json detected for $url: $data"; + return "NUKIDevice ($name) - invalid json detected for $url: $data"; + } + + + my $decode_json = decode_json($data); + + return undef if( !$decode_json ); + + Log3 $name, 5, "NUKIBridge ($name) - Data: $data"; + Log3 $name, 4, "NUKIBridge ($name) - Blocking HTTP Abfrage beendet"; + return ($decode_json); +} + + + + 1; @@ -483,11 +677,12 @@ sub NUKIBridge_Autocreate($$;$) { =pod =item device -=item summary +=item summary Modul to control the Nuki Smartlock's over the Nuki Bridge. =item summary_DE Modul zur Steuerung des Nuki Smartlock über die Nuki Bridge. =begin html +timestamp

NUKIBridge

@@ -524,7 +728,19 @@ sub NUKIBridge_Autocreate($$;$) { Set +

+ + Get +

@@ -532,7 +748,6 @@ sub NUKIBridge_Autocreate($$;$) { Attributes @@ -568,6 +783,15 @@ sub NUKIBridge_Autocreate($$;$) {
  • 0_name - Name des ersten gefunden Nuki Smartlocks
  • smartlockCount - Anzahl aller gefundenen Smartlock
  • bridgeAPI - API Version der Bridge
  • +
  • bridgeType - Hardware oder Software/App Bridge
  • +
  • currentTime - aktuelle Zeit auf der Bridge zum zeitpunkt des Info holens
  • +
  • firmwareVersion - aktuell auf der Bridge verwendete Firmwareversion
  • +
  • hardwareId - ID der Hardware Bridge
  • +
  • lastError - gibt die letzte HTTP Errormeldung wieder
  • +
  • serverConnected - true/false gibt an ob die Hardwarebridge Verbindung zur Nuki-Cloude hat.
  • +
  • serverId - gibt die ID des Cloudeservers wieder
  • +
  • uptime - Uptime der Bridge in Sekunden
  • +
  • wifiFirmwareVersion- Firmwareversion des Wifi Modules der Bridge

  • Die vorangestellte Zahl ist forlaufend und gibt beginnend bei 0 die Eigenschaften Eines Smartlocks wieder. @@ -576,7 +800,19 @@ sub NUKIBridge_Autocreate($$;$) { Set +

    + + Get +

    @@ -584,10 +820,9 @@ sub NUKIBridge_Autocreate($$;$) { Attribute =end html_DE -=cut \ No newline at end of file +=cut diff --git a/fhem/FHEM/74_NUKIDevice.pm b/fhem/FHEM/74_NUKIDevice.pm index eb71e190a..7708dd65e 100644 --- a/fhem/FHEM/74_NUKIDevice.pm +++ b/fhem/FHEM/74_NUKIDevice.pm @@ -33,7 +33,7 @@ use warnings; use JSON; #use Time::HiRes qw(gettimeofday); -my $version = "0.2.1"; +my $version = "0.4.0"; @@ -42,21 +42,24 @@ sub NUKIDevice_Initialize($) { my ($hash) = @_; - $hash->{SetFn} = "NUKIDevice_Set"; - $hash->{DefFn} = "NUKIDevice_Define"; - $hash->{UndefFn} = "NUKIDevice_Undef"; - $hash->{AttrFn} = "NUKIDevice_Attr"; + $hash->{SetFn} = "NUKIDevice_Set"; + $hash->{DefFn} = "NUKIDevice_Define"; + $hash->{UndefFn} = "NUKIDevice_Undef"; + $hash->{AttrFn} = "NUKIDevice_Attr"; - $hash->{AttrList} = "IODev ". + my $webhookFWinstance = join( ",", devspec2array('TYPE=FHEMWEB:FILTER=TEMPORARY!=1') ); + + $hash->{AttrList} = "IODev ". "disable:1 ". - "interval ". + "webhookFWinstance:$webhookFWinstance ". + "webhookHttpHostname ". $readingFnAttributes; foreach my $d(sort keys %{$modules{NUKIDevice}{defptr}}) { - my $hash = $modules{NUKIDevice}{defptr}{$d}; - $hash->{VERSION} = $version; + my $hash = $modules{NUKIDevice}{defptr}{$d}; + $hash->{VERSION} = $version; } } @@ -83,10 +86,10 @@ sub NUKIDevice_Define($$) { my ($name,$nukiId) = @a; - $hash->{NUKIID} = $nukiId; - $hash->{VERSION} = $version; + $hash->{NUKIID} = $nukiId; + $hash->{VERSION} = $version; $hash->{STATE} = 'Initialized'; - $hash->{INTERVAL} = 20; + my $infix = "NUKIDevice"; AssignIoPort($hash,$iodev) if( !$hash->{IODev} ); @@ -117,13 +120,18 @@ sub NUKIDevice_Define($$) { $attr{$name}{room} = "NUKI" if( !defined( $attr{$name}{room} ) ); + if ( NUKIDevice_addExtension( $name, "NUKIDevice_CGI", $infix ) ) { + $hash->{fhem}{infix} = $infix; + } + + $hash->{WEBHOOK_REGISTER} = "unregistered"; + - RemoveInternalTimer($hash); if( $init_done ) { - NUKIDevice_GetUpdateInternalTimer($hash); + InternalTimer( gettimeofday()+int(rand(10)), "NUKIDevice_GetUpdate", $hash, 0 ); } else { - InternalTimer(gettimeofday()+20, "NUKIDevice_GetUpdateInternalTimer", $hash, 0); + InternalTimer( gettimeofday()+15+int(rand(5)), "NUKIDevice_GetUpdate", $hash, 0 ); } return undef; @@ -137,12 +145,16 @@ sub NUKIDevice_Undef($$) { my $name = $hash->{NAME}; + if ( defined( $hash->{fhem}{infix} ) ) { + NUKIDevice_removeExtension( $hash->{fhem}{infix} ); + } + RemoveInternalTimer($hash); my $code = $hash->{NUKIID}; $code = $hash->{IODev}->{NAME} ."-". $code if( defined($hash->{IODev}->{NAME}) ); Log3 $name, 3, "NUKIDevice ($name) - undefined with Code: $code"; - delete($modules{HUEDevice}{defptr}{$code}); + delete($modules{NUKIDevice}{defptr}{$code}); return undef; } @@ -151,49 +163,102 @@ sub NUKIDevice_Attr(@) { my ( $cmd, $name, $attrName, $attrVal ) = @_; my $hash = $defs{$name}; + my $token = $hash->{IODev}->{TOKEN}; if( $attrName eq "disable" ) { - if( $cmd eq "set" ) { - if( $attrVal eq "0" ) { - RemoveInternalTimer( $hash ); - InternalTimer( gettimeofday()+2, "NUKIDevice_GetUpdateInternalTimer", $hash, 0 ); - readingsSingleUpdate ( $hash, "state", "Initialized", 1 ); - Log3 $name, 3, "NUKIDevice ($name) - enabled"; - } else { - readingsSingleUpdate ( $hash, "state", "disabled", 1 ); - RemoveInternalTimer( $hash ); - Log3 $name, 3, "NUKIDevice ($name) - disabled"; - } - - } else { - - RemoveInternalTimer( $hash ); - InternalTimer( gettimeofday()+2, "NUKIDevice_GetUpdateInternalTimer", $hash, 0 ); - readingsSingleUpdate ( $hash, "state", "Initialized", 1 ); - Log3 $name, 3, "NUKIDevice ($name) - enabled"; + if( $cmd eq "set" and $attrVal eq "1" ) { + readingsSingleUpdate ( $hash, "state", "disabled", 1 ); + Log3 $name, 3, "NUKIDevice ($name) - disabled"; + } + + elsif( $cmd eq "del" ) { + readingsSingleUpdate ( $hash, "state", "active", 1 ); + Log3 $name, 3, "NUKIDevice ($name) - enabled"; } } - if( $attrName eq "interval" ) { - if( $cmd eq "set" ) { - if( $attrVal < 10 ) { - Log3 $name, 3, "NUKIDevice ($name) - interval too small, please use something > 10 (sec), default is 20 (sec)"; - return "interval too small, please use something > 10 (sec), default is 60 (sec)"; - } else { - $hash->{INTERVAL} = $attrVal; - Log3 $name, 3, "NUKIDevice ($name) - set interval to $attrVal"; - } - - } else { - - $hash->{INTERVAL} = 20; - Log3 $name, 3, "NUKIDevice ($name) - set interval to default"; + if( $attrName eq "disabledForIntervals" ) { + if( $cmd eq "set" ) { + Log3 $name, 3, "NUKIDevice ($name) - enable disabledForIntervals"; + readingsSingleUpdate ( $hash, "state", "Unknown", 1 ); + } + + elsif( $cmd eq "del" ) { + readingsSingleUpdate ( $hash, "state", "active", 1 ); + Log3 $name, 3, "NUKIDevice ($name) - delete disabledForIntervals"; + } + } + + ###################### + #### webhook ######### + + return "Invalid value for attribute $attrName: can only by FQDN or IPv4 or IPv6 address" if ( $attrVal && $attrName eq "webhookHttpHostname" && $attrVal !~ /^([A-Za-z_.0-9]+\.[A-Za-z_.0-9]+)|[0-9:]+$/ ); + + return "Invalid value for attribute $attrName: needs to be different from the defined name/address of your Smartlock, we need to know how Smartlock can connect back to FHEM here!" if ( $attrVal && $attrName eq "webhookHttpHostname" && $attrVal eq $hash->{DeviceName} ); + + return "Invalid value for attribute $attrName: FHEMWEB instance $attrVal not existing" if ( $attrVal && $attrName eq "webhookFWinstance" && ( !defined( $defs{$attrVal} ) || $defs{$attrVal}{TYPE} ne "FHEMWEB" ) ); + + return "Invalid value for attribute $attrName: needs to be an integer value" if ( $attrVal && $attrName eq "webhookPort" && $attrVal !~ /^\d+$/ ); + + + + + if ( $attrName =~ /^webhook.*/ ) { + + my $webhookHttpHostname = ( $attrName eq "webhookHttpHostname" ? $attrVal : AttrVal( $name, "webhookHttpHostname", "" ) ); + my $webhookFWinstance = ( $attrName eq "webhookFWinstance" ? $attrVal : AttrVal( $name, "webhookFWinstance", "" ) ); + + $hash->{WEBHOOK_URI} = "/" . AttrVal( $webhookFWinstance, "webname", "fhem" ) . "/NUKIDevice"; + $hash->{WEBHOOK_PORT} = ( $attrName eq "webhookPort" ? $attrVal : AttrVal( $name, "webhookPort", InternalVal( $webhookFWinstance, "PORT", "" )) ); + + $hash->{WEBHOOK_URL} = ""; + $hash->{WEBHOOK_COUNTER} = "0"; + + if ( $webhookHttpHostname ne "" && $hash->{WEBHOOK_PORT} ne "" ) { + + $hash->{WEBHOOK_URL} = "http://" . $webhookHttpHostname . ":" . $hash->{WEBHOOK_PORT} . $hash->{WEBHOOK_URI} . "-" . $hash->{NUKIID}; + my $url = "http://$webhookHttpHostname" . ":" . $hash->{WEBHOOK_PORT} . $hash->{WEBHOOK_URI} . "-" . $hash->{NUKIID}; + + Log3 $name, 3, "NUKIDevice ($name) - URL ist: $url"; + NUKIDevice_ReadFromNUKIBridge($hash,"callback/add",$url,undef ) if( $init_done ); + $hash->{WEBHOOK_REGISTER} = "sent"; + + } else { + $hash->{WEBHOOK_REGISTER} = "incomplete_attributes"; } } return undef; } +sub NUKIDevice_addExtension($$$) { + + my ( $name, $func, $link ) = @_; + my $url = "/$link"; + + + return 0 if ( defined( $data{FWEXT}{$url} ) && $data{FWEXT}{$url}{deviceName} ne $name ); + + Log3 $name, 2, "NUKIDevice ($name) - Registering NUKIDevice for webhook URI $url ..."; + + $data{FWEXT}{$url}{deviceName} = $name; + $data{FWEXT}{$url}{FUNC} = $func; + $data{FWEXT}{$url}{LINK} = $link; + + return 1; +} + +sub NUKIDevice_removeExtension($) { + + my ($link) = @_; + + my $url = "/$link"; + my $name = $data{FWEXT}{$url}{deviceName}; + + Log3 $name, 2, "NUKIDevice ($name) - Unregistering NUKIDevice for webhook URL $url..."; + delete $data{FWEXT}{$url}; +} + sub NUKIDevice_Set($$@) { my ($hash, $name, @aa) = @_; @@ -230,7 +295,7 @@ sub NUKIDevice_Set($$@) { } $hash->{helper}{lockAction} = $lockAction; - NUKIDevice_ReadFromNUKIBridge($hash,"lockAction",$lockAction,$hash->{NUKIID} ); + NUKIDevice_ReadFromNUKIBridge($hash,"lockAction",$lockAction,$hash->{NUKIID} ) if( !IsDisabled($name) ); return undef; } @@ -240,36 +305,29 @@ sub NUKIDevice_GetUpdate($) { my ($hash) = @_; my $name = $hash->{NAME}; + RemoveInternalTimer($hash); - NUKIDevice_ReadFromNUKIBridge($hash, "lockState", undef, $hash->{NUKIID} ); - Log3 $name, 5, "NUKIDevice ($name) - NUKIDevice_GetUpdate Call NUKIDevice_ReadFromNUKIBridge"; + NUKIDevice_ReadFromNUKIBridge($hash, "lockState", undef, $hash->{NUKIID} ) if( !IsDisabled($name) ); + Log3 $name, 5, "NUKIDevice ($name) - NUKIDevice_GetUpdate Call NUKIDevice_ReadFromNUKIBridge" if( !IsDisabled($name) ); return undef; } -sub NUKIDevice_GetUpdateInternalTimer($) { - - my ($hash) = @_; - my $name = $hash->{NAME}; - - - NUKIDevice_GetUpdate($hash); - Log3 $name, 5, "NUKIDevice ($name) - Call NUKIDevice_GetUpdate"; - - RemoveInternalTimer($hash); - InternalTimer(gettimeofday()+$hash->{INTERVAL}, "NUKIDevice_GetUpdateInternalTimer", $hash, 0) if( $hash->{INTERVAL} ); - Log3 $name, 5, "NUKIDevice ($name) - Call InternalTimer"; -} - sub NUKIDevice_ReadFromNUKIBridge($@) { my ($hash,@a) = @_; my $name = $hash->{NAME}; + Log3 $name, 4, "NUKIDevice ($name) - NUKIDevice_ReadFromNUKIBridge check Bridge connected"; + return "IODev $hash->{IODev} is not connected" if( ReadingsVal($hash->{IODev}->{NAME},"state","not connected") eq "not connected" ); + + no strict "refs"; my $ret; unshift(@a,$name); + Log3 $name, 4, "NUKIDevice ($name) - NUKIDevice_ReadFromNUKIBridge Bridge is connected call IOWrite"; + $ret = IOWrite($hash,$hash,@a); use strict "refs"; return $ret; @@ -317,15 +375,23 @@ sub NUKIDevice_Parse($$) { ######################################### #### verarbeiten des JSON Strings ####### - my $decode_json = decode_json($result); + if( ref($decode_json) ne "HASH" ) { - Log3 $name, 2, "$name: got wrong status message for $name: $decode_json"; + Log3 $name, 2, "NUKIDevice ($name) - got wrong status message for $name: $decode_json"; return undef; } - Log3 $name, 5, "parse status message for $name"; + Log3 $name, 5, "NUKIDevice ($name) - parse status message for $name"; + + NUKIDevice_WriteReadings($hash,$decode_json); +} + +sub NUKIDevice_WriteReadings($$) { + + my ($hash,$decode_json) = @_; + my $name = $hash->{NAME}; ############################ @@ -335,10 +401,14 @@ sub NUKIDevice_Parse($$) { my $battery; - if( $decode_json->{batteryCritical} eq "false" ) { - $battery = "ok"; - } else { - $battery = "low"; + if( defined($decode_json->{batteryCritical}) ) { + if( $decode_json->{batteryCritical} eq "false" ) { + $battery = "ok"; + } elsif ( $decode_json->{batteryCritical} eq "true" ) { + $battery = "low"; + } else { + $battery = "parseError"; + } } if( defined($hash->{helper}{lockAction}) ) { @@ -357,6 +427,7 @@ sub NUKIDevice_Parse($$) { readingsBulkUpdate( $hash, "battery", $battery ); delete $hash->{helper}{lockAction}; + Log3 $name, 5, "NUKIDevice ($name) - readings set for $name"; } else { @@ -366,7 +437,7 @@ sub NUKIDevice_Parse($$) { readingsBulkUpdate( $hash, "battery", $battery ); readingsBulkUpdate( $hash, "success", $decode_json->{success} ); - Log3 $name, 5, "readings set for $name"; + Log3 $name, 5, "NUKIDevice ($name) - readings set for $name"; } readingsEndUpdate( $hash, 1 ); @@ -374,6 +445,58 @@ sub NUKIDevice_Parse($$) { return undef; } +sub NUKIDevice_CGI() { + + my ($request) = @_; + + my $hash; + my $name; + my $nukiId; + + # data received + # Testaufruf: wget --post-data '{"nukiId": 123456, "state": 1,"stateName": "locked", "batteryCritical": false}' http://10.6.6.20:8083/fhem/NUKIDevice-123456 + + + my $header = join("\n", @FW_httpheader); + + my ($first,$json) = split("&",$request,2); + my $decode_json = decode_json($json); + + + if( ref($decode_json) eq "HASH" ) { + if ( defined( $modules{NUKIDevice}{defptr} ) ) { + while ( my ( $key, $value ) = each %{ $modules{NUKIDevice}{defptr} } ) { + + $hash = $modules{NUKIDevice}{defptr}{$key}; + $name = $hash->{NAME}; + $nukiId = InternalVal( $name, "NUKIID", undef ); + next if ( !$nukiId or $nukiId ne $decode_json->{nukiId} ); + + $hash->{WEBHOOK_COUNTER}++; + $hash->{WEBHOOK_LAST} = TimeNow(); + + Log3 $name, 4, "NUKIDevice ($name) - Received webhook for matching NukiId at device $name"; + + NUKIDevice_Parse($hash,$json); + } + } + + return ( undef, undef ); + } + + # no data received + else { + + Log3 undef, 4, "NUKIDevice - received malformed request\n$request"; + } + + return ( "text/plain; charset=utf-8", "Call failure: " . $request ); +} + + + + + 1; @@ -383,7 +506,7 @@ sub NUKIDevice_Parse($$) { =pod =item device -=item summary +=item summary Modul to control the Nuki Smartlock's =item summary_DE Modul zur Steuerung des Nuki Smartlocks. =begin html @@ -436,7 +559,8 @@ sub NUKIDevice_Parse($$) { Attributes @@ -492,10 +616,11 @@ sub NUKIDevice_Parse($$) { Attribute =end html_DE -=cut \ No newline at end of file +=cut