From c21bd2479a1d0303c3753979277739168e256ce2 Mon Sep 17 00:00:00 2001 From: Marko Oldenburg Date: Sat, 27 Nov 2021 09:52:55 +0100 Subject: [PATCH 1/2] fix uninitialized value $iodev in hash element line 221 --- controls_NukiSmart.txt | 8 ++++---- lib/FHEM/Devices/Nuki/Device.pm | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/controls_NukiSmart.txt b/controls_NukiSmart.txt index dba6012..5f71fd0 100644 --- a/controls_NukiSmart.txt +++ b/controls_NukiSmart.txt @@ -1,4 +1,4 @@ -UPD 2021-11-26_17:16:05 9207 FHEM/73_NUKIBridge.pm -UPD 2021-11-26_18:16:04 7548 FHEM/74_NUKIDevice.pm -UPD 2021-11-26_19:31:40 35495 lib/FHEM/Devices/Nuki/Bridge.pm -UPD 2021-11-26_18:46:30 15635 lib/FHEM/Devices/Nuki/Device.pm +UPD 2021-11-27_08:19:45 9207 FHEM/73_NUKIBridge.pm +UPD 2021-11-27_08:19:45 7548 FHEM/74_NUKIDevice.pm +UPD 2021-11-27_08:19:45 35495 lib/FHEM/Devices/Nuki/Bridge.pm +UPD 2021-11-27_09:51:08 15681 lib/FHEM/Devices/Nuki/Device.pm diff --git a/lib/FHEM/Devices/Nuki/Device.pm b/lib/FHEM/Devices/Nuki/Device.pm index 681852f..d6660a8 100644 --- a/lib/FHEM/Devices/Nuki/Device.pm +++ b/lib/FHEM/Devices/Nuki/Device.pm @@ -218,7 +218,9 @@ sub Define { $iodev = $hash->{IODev}->{NAME}; - $hash->{BRIDGEAPI} = $::defs{$iodev}->{BRIDGEAPI}; + $hash->{BRIDGEAPI} = $::defs{$iodev}->{BRIDGEAPI} + if ( defined(iodev) + && $iodev ); my $d = $::modules{NUKIDevice}{defptr}{$nukiId}; -- 2.45.2 From a8ea6814abd6c9b650be504cc2b58553f1cdd062 Mon Sep 17 00:00:00 2001 From: Marko Oldenburg Date: Sat, 27 Nov 2021 18:08:27 +0100 Subject: [PATCH 2/2] add discovery and full automation define bridge with host and port from fetch API information via /auth endpoint --- FHEM/73_NUKIBridge.pm | 1 + controls_NukiSmart.txt | 8 +- lib/FHEM/Devices/Nuki/Bridge.pm | 208 ++++++++++++++++++++++++++++---- lib/FHEM/Devices/Nuki/Device.pm | 30 ++--- 4 files changed, 205 insertions(+), 42 deletions(-) diff --git a/FHEM/73_NUKIBridge.pm b/FHEM/73_NUKIBridge.pm index a4234ee..7869c1b 100644 --- a/FHEM/73_NUKIBridge.pm +++ b/FHEM/73_NUKIBridge.pm @@ -65,6 +65,7 @@ sub Initialize { $hash->{AttrFn} = \&FHEM::Devices::Nuki::Bridge::Attr; $hash->{AttrList} = 'disable:1 ' + . 'port ' . 'webhookFWinstance:' . $webhookFWinstance . ' ' . 'webhookHttpHostname ' diff --git a/controls_NukiSmart.txt b/controls_NukiSmart.txt index 5f71fd0..1bcbe69 100644 --- a/controls_NukiSmart.txt +++ b/controls_NukiSmart.txt @@ -1,4 +1,4 @@ -UPD 2021-11-27_08:19:45 9207 FHEM/73_NUKIBridge.pm -UPD 2021-11-27_08:19:45 7548 FHEM/74_NUKIDevice.pm -UPD 2021-11-27_08:19:45 35495 lib/FHEM/Devices/Nuki/Bridge.pm -UPD 2021-11-27_09:51:08 15681 lib/FHEM/Devices/Nuki/Device.pm +UPD 2021-11-27_18:02:39 9224 FHEM/73_NUKIBridge.pm +UPD 2021-11-27_18:01:59 7548 FHEM/74_NUKIDevice.pm +UPD 2021-11-27_18:08:11 40444 lib/FHEM/Devices/Nuki/Bridge.pm +UPD 2021-11-27_15:28:59 15728 lib/FHEM/Devices/Nuki/Device.pm diff --git a/lib/FHEM/Devices/Nuki/Bridge.pm b/lib/FHEM/Devices/Nuki/Bridge.pm index 3d27c59..a5e042c 100644 --- a/lib/FHEM/Devices/Nuki/Bridge.pm +++ b/lib/FHEM/Devices/Nuki/Bridge.pm @@ -145,28 +145,38 @@ sub Define { use version 0.60; our $VERSION = FHEM::Meta::Get( $hash, 'version' ); my ( $name, undef, $host, $token ) = split( m{\s+}xms, $def ); - return ('too few parameters: define NUKIBridge ') - if ( !defined($host) - || !defined($token) ); +# return ('too few parameters: define NUKIBridge ') +# if ( !defined($host) +# || !defined($token) ); - my $port = 8080; + my $port = ::AttrVal($name, 'port', 8080); my $infix = 'NUKIBridge'; - $hash->{HOST} = $host; + $hash->{HOST} = $host // 'discover'; $hash->{PORT} = $port; - $hash->{TOKEN} = $token; + $hash->{TOKEN} = $token // 'discover'; $hash->{NOTIFYDEV} = 'global,' . $name; $hash->{VERSION} = version->parse($VERSION)->normal; $hash->{BRIDGEAPI} = FHEM::Meta::Get( $hash, 'x_apiversion' ); $hash->{helper}->{actionQueue} = []; $hash->{helper}->{iowrite} = 0; - ::Log3( $name, 3, -"NUKIBridge ($name) - defined with host $host on port $port, Token $token" - ); - ::CommandAttr( undef, $name . ' room NUKI' ) if ( ::AttrVal( $name, 'room', 'none' ) eq 'none' ); + $hash->{WEBHOOK_REGISTER} = "unregistered"; + + ::readingsSingleUpdate($hash, 'state', 'Initialized', 1); + + ::RemoveInternalTimer($hash); + + return BridgeDiscover($hash,'discover') + if ( $hash->{HOST} eq 'discover' + && $hash->{TOKEN} eq 'discover' ); + + ::Log3( $name, 3, +"NUKIBridge ($name) - defined with host $host on port $port, Token $token" + ); + if ( addExtension( $name, @@ -177,13 +187,7 @@ sub Define { { $hash->{fhem}{infix} = $infix; } - - $hash->{WEBHOOK_REGISTER} = "unregistered"; - - ::readingsSingleUpdate( $hash, 'state', 'Initialized', 1 ); - - ::RemoveInternalTimer($hash); - + $::modules{NUKIBridge}{defptr}{ $hash->{HOST} } = $hash; return; @@ -216,23 +220,34 @@ sub Attr { if ( $attrName eq 'disable' ) { if ( $cmd eq 'set' && $attrVal == 1 ) { - ::readingsSingleUpdate( $hash, 'state', 'disabled', 1 ); + ::readingsSingleUpdate($hash, 'state', 'disabled', 1); ::Log3( $name, 3, "NUKIBridge ($name) - disabled" ); } elsif ( $cmd eq 'del' ) { - ::readingsSingleUpdate( $hash, 'state', 'active', 1 ); + ::readingsSingleUpdate($hash, 'state', 'active', 1); ::Log3( $name, 3, "NUKIBridge ($name) - enabled" ); } } + + if ( $attrName eq 'port' ) { + if ( $cmd eq 'set' ) { + $hash->{PORT} = $attrVal; + ::Log3( $name, 3, "NUKIBridge ($name) - change bridge port" ); + } + elsif ( $cmd eq 'del' ) { + $hash->{PORT} = 8080; + ::Log3( $name, 3, "NUKIBridge ($name) - set bridge port to default" ); + } + } if ( $attrName eq 'disabledForIntervals' ) { if ( $cmd eq 'set' ) { ::Log3( $name, 3, "NUKIBridge ($name) - enable disabledForIntervals" ); - ::readingsSingleUpdate( $hash, 'state', 'Unknown', 1 ); + ::readingsSingleUpdate($hash, 'state', 'Unknown', 1); } elsif ( $cmd eq 'del' ) { - ::readingsSingleUpdate( $hash, 'state', 'active', 1 ); + ::readingsSingleUpdate($hash, 'state', 'active', 1); ::Log3( $name, 3, "NUKIBridge ($name) - delete disabledForIntervals" ); } @@ -345,6 +360,8 @@ sub Notify { or grep /^DEFINED.$name$/, @{$events} ) + && $hash->{HOST} ne 'discover' + && $hash->{TOKEN} ne 'discover' && $devname eq 'global' && $::init_done ); @@ -378,7 +395,10 @@ sub removeExtension { ::Log3( $name, 2, "NUKIBridge ($name) - Unregistering NUKIBridge for webhook URL $url..." - ); + ) + if ( defined($name) ); + + delete $::data{FWEXT}{$url}; return; @@ -636,7 +656,7 @@ sub Distribution { my $json = shift; my $hash = $param->{hash}; - my $doTrigger = $param->{doTrigger}; +# my $doTrigger = $param->{doTrigger}; my $name = $hash->{NAME}; my $host = $hash->{HOST}; @@ -759,7 +779,7 @@ sub Distribution { ::readingsEndUpdate( $hash, 1 ); - ::readingsSingleUpdate( $hash, 'state', 'connected', 1 ); + ::readingsSingleUpdate($hash, 'state', 'connected', 1); ::Log3( $name, 5, "NUKIBridge ($name) - Bridge ist online" ); if ( $param->{endpoint} eq 'callback/list' ) { @@ -1251,4 +1271,144 @@ sub ParseJSON { return ( $msg, $tail ); } +sub BridgeDiscover { + my $hash = shift; + my $endpoint = shift; + my $bridge = shift; + my $name = $hash->{NAME}; + my $url = ( $endpoint eq 'discover' && !defined($bridge) + ? 'https://api.nuki.io/discover/bridges' + : 'http://' . $bridge->{'ip'} . ':' . $bridge->{'port'} . '/auth' ); + my $timeout = ( $endpoint eq 'discover' && !defined($bridge) + ? 5 + : 35 ); + + if ( $endpoint eq 'discover' ) { + ::Log3( $name, 3, + "NUKIBridge ($name) - Bridge device defined. run discover mode" + ); + + ::readingsSingleUpdate($hash, 'state', 'run discovery', 1); + } + elsif ( $endpoint eq 'getApiToken' ) { + + ::Log3( $name, 3, + "NUKIBridge ($name) - Enables the api (if not yet enabled) and get the api token." + ); + } + + + ::HttpUtils_NonblockingGet( + { + url => $url, + timeout => $timeout, + hash => $hash, + header => 'Accept: application/json', + endpoint => $endpoint, + host => $bridge->{'ip'}, + port => $bridge->{'port'}, + method => 'GET', + callback => \&BridgeDiscoverRequest, + } + ); + + ::Log3( $name, 3, + "NUKIBridge ($name) - Send Discover request to Nuki Cloud" ) + if ( $endpoint eq 'discover' ); + + ::Log3( $name, 3, + "NUKIBridge ($name) - get API Token from the Bridge" ) + if ( $endpoint eq 'getApiToken' ); + + return; +} + +sub BridgeDiscoverRequest { + my $param = shift; + my $err = shift; + my $json = shift; + + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + + + if ( defined($err) + && $err ne '' ) + { + return ::Log3( $name, 3, + "NUKIBridge ($name) - Error: $err"); + } + elsif ( exists( $param->{code}) + && $param->{code} != 200 ) + { + return ::Log3( $name, 3, + "NUKIBridge ($name) - HTTP error Code present. Code: $param->{code}"); + } + + my $decode_json; + $decode_json = eval { decode_json($json) }; + if ($@) { + ::Log3( $name, 3, "NUKIBridge ($name) - JSON error while request: $@" ); + return; + } + + if ( $param->{endpoint} eq 'discover' ) { + + return ::readingsSingleUpdate($hash, 'state', 'no bridges discovered', 1) + if ( scalar(@{$decode_json->{bridges}}) == 0 + && $decode_json->{errorCode} == 0 ); + + return BridgeDiscover_getAPIToken($hash,$decode_json); + } + elsif ( $param->{endpoint} eq 'getApiToken' ) { + ::readingsSingleUpdate($hash, 'state', 'modefined bridge device in progress', 1); + + $decode_json->{host} = $param->{host}; + $decode_json->{port} = $param->{port}; + + return ModefinedBridgeDevices($hash,$decode_json) + if ( $decode_json->{success} == 1 ); + + return ::readingsSingleUpdate($hash, 'state', 'get api token failed', 1); + } + + return; +} + +sub BridgeDiscover_getAPIToken { + my $hash = shift; + my $decode_json = shift; + my $name = $hash->{NAME}; + + my $pullApiKeyMessage = 'When issuing this API-call the bridge turns on its LED for 30 seconds. +The button of the bridge has to be pressed within this timeframe. Otherwise the bridge returns a negative success and no token.'; + + ::readingsSingleUpdate($hash, 'state', $pullApiKeyMessage, 1); + + for ( @{$decode_json->{bridges}} ) { + + BridgeDiscover($hash,'getApiToken',$_); + } + + return; +} + +sub ModefinedBridgeDevices { + my $hash = shift; + my $decode_json = shift; + my $name = $hash->{NAME}; + + + ::CommandAttr( undef, $name . ' port ' . $decode_json->{port} ) + if ( $decode_json->{port} != 8080 ); + ::CommandDefMod( undef, + $name + . ' NUKIBridge ' + . $decode_json->{host} . ' ' + . $decode_json->{token} ); + + return; +} + + 1; diff --git a/lib/FHEM/Devices/Nuki/Device.pm b/lib/FHEM/Devices/Nuki/Device.pm index d6660a8..065dbb5 100644 --- a/lib/FHEM/Devices/Nuki/Device.pm +++ b/lib/FHEM/Devices/Nuki/Device.pm @@ -219,7 +219,7 @@ sub Define { $iodev = $hash->{IODev}->{NAME}; $hash->{BRIDGEAPI} = $::defs{$iodev}->{BRIDGEAPI} - if ( defined(iodev) + if ( defined($iodev) && $iodev ); my $d = $::modules{NUKIDevice}{defptr}{$nukiId}; @@ -446,23 +446,25 @@ sub Parse { WriteReadings( $hash, $decode_json ); ::Log3( $name, 4, "NUKIDevice ($name) - find logical device: $hash->{NAME}" ); + + return $hash->{NAME}; ################## ## Zwischenlösung so für die Umstellung, kann später gelöscht werden - if ( ::AttrVal( $name, 'model', '' ) eq '' ) { - ::CommandDefMod( undef, - $name - . ' NUKIDevice ' - . $hash->{NUKIID} . ' ' - . $decode_json->{deviceType} ); - ::CommandAttr( undef, - $name - . ' model ' - . $deviceTypes{ $decode_json->{deviceType} } ); - ::Log3( $name, 2, "NUKIDevice ($name) - redefined Defmod" ); - } +# if ( ::AttrVal( $name, 'model', '' ) eq '' ) { +# ::CommandDefMod( undef, +# $name +# . ' NUKIDevice ' +# . $hash->{NUKIID} . ' ' +# . $decode_json->{deviceType} ); +# ::CommandAttr( undef, +# $name +# . ' model ' +# . $deviceTypes{ $decode_json->{deviceType} } ); +# ::Log3( $name, 2, "NUKIDevice ($name) - redefined Defmod" ); +# } - return $hash->{NAME}; + } else { ::Log3( $name, 4, -- 2.45.2