From b6f8484872815e76d1b20e361796d4811d1707bc Mon Sep 17 00:00:00 2001 From: rapster <> Date: Sat, 29 Aug 2015 19:25:28 +0000 Subject: [PATCH] 74_Unifi: - Code Cleanup - Preparing for future functions - Some small bug fixes git-svn-id: https://svn.fhem.de/fhem/trunk@9160 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/74_Unifi.pm | 496 ++++++++++++++++++++++++++++-------------- 1 file changed, 333 insertions(+), 163 deletions(-) diff --git a/fhem/FHEM/74_Unifi.pm b/fhem/FHEM/74_Unifi.pm index f942c8afc..c1dce3717 100644 --- a/fhem/FHEM/74_Unifi.pm +++ b/fhem/FHEM/74_Unifi.pm @@ -1,11 +1,11 @@ ############################################################################### -# $Id: 74_Unifi.pm 2015-08-27 06:00 - rapster - rapster at x0e dot de $ +# $Id: 74_Unifi.pm 2015-08-29 21:00 - rapster - rapster at x0e dot de $ package main; use strict; use warnings; use HttpUtils; -use POSIX qw(strftime); +use POSIX; use JSON qw(decode_json); ############################################################################### @@ -16,7 +16,6 @@ sub Unifi_Initialize($$) { $hash->{SetFn} = "Unifi_Set"; $hash->{GetFn} = "Unifi_Get"; $hash->{AttrFn} = 'Unifi_Attr'; - $hash->{NOTIFYDEV} = "global"; $hash->{NotifyFn} = "Unifi_Notify"; $hash->{AttrList} = "disable:1,0 " ."devAlias " @@ -34,13 +33,14 @@ sub Unifi_Define($$) { return "Wrong syntax: is not a valid number! Must be 3 or 4." if($a[8] && (!looks_like_number($a[8]) || $a[8] !~ /3|4/)); my $name = $a[0]; - my $oldLoginData = ($hash->{loginParams}) ? $hash->{loginParams}->{data}.$hash->{url} : 0; %$hash = ( %$hash, - CONNECTED => $hash->{CONNECTED} || 0, - url => "https://".$a[2].(($a[3] != 443) ? ':'.$a[3] : '').'/', - interval => $a[6] || 30, - siteID => $a[7] || 'default', - version => $a[8] || 4, + NOTIFYDEV => 'global', + unifi => { + CONNECTED => 0, + interval => $a[6] || 30, + version => $a[8] || 4, + url => "https://".$a[2].(($a[3] == 443) ? '' : ':'.$a[3]).'/api/s/'.(($a[7]) ? $a[7] : 'default').'/', + }, ); $hash->{httpParams} = { hash => $hash, @@ -50,28 +50,16 @@ sub Unifi_Define($$) { ignoreredirects => 1, loglevel => 5, sslargs => { SSL_verify_mode => 'SSL_VERIFY_NONE' }, - header => ($hash->{version} == 3) ? undef : "Content-Type: application/json;charset=UTF-8" }; - $hash->{loginParams} = { - %{$hash->{httpParams}}, - cookies => ($hash->{loginParams}->{cookies}) ? $hash->{loginParams}->{cookies} : '', - callback => \&Unifi_Login_Receive - }; - if($hash->{version} == 3) { - $hash->{loginParams}->{url} = $hash->{url}."login"; - $hash->{loginParams}->{data} = "login=login&username=".Unifi_Urlencode($a[4])."&password=".Unifi_Urlencode($a[5]); + if($hash->{unifi}->{version} == 3) { + ( $hash->{httpParams}->{loginUrl} = $hash->{unifi}->{url} ) =~ s/api\/s.+/login/; + $hash->{httpParams}->{loginData} = "login=login&username=".$a[4]."&password=".$a[5]; }else { - $hash->{loginParams}->{url} = $hash->{url}."api/login"; - $hash->{loginParams}->{data} = "{'username':'".$a[4]."', 'password':'".$a[5]."'}"; + ( $hash->{httpParams}->{loginUrl} = $hash->{unifi}->{url} ) =~ s/api\/s.+/api\/login/; + $hash->{httpParams}->{loginData} = '{"username":"'.$a[4].'", "password":"'.$a[5].'"}'; } - # Don't use old cookies when user, pw or url changed - if($oldLoginData && $oldLoginData ne $hash->{loginParams}->{data}.$hash->{url}) { - $hash->{loginParams}->{cookies} = ''; - Unifi_CONNECTED($hash,'disconnected'); - } - - Log3 $name, 5, "$name: Defined with url:$hash->{url}, interval:$hash->{interval}, siteID:$hash->{siteID}, version:$hash->{version}"; + Log3 $name, 5, "$name: Defined with url:$hash->{unifi}->{url}, interval:$hash->{unifi}->{interval}, version:$hash->{unifi}->{version}"; return undef; } ############################################################################### @@ -149,7 +137,7 @@ sub Unifi_Get($@) { my $clients = ''; my $devAliases = AttrVal($name,"devAlias",0); for (keys %{$hash->{clients}}) { # Replace ID's with Aliases - if ( $devAliases && $devAliases =~ /$_:(.+?)(\s|$)/ + if ( ($devAliases && $devAliases =~ /$_:(.+?)(\s|$)/) || ($devAliases && defined $hash->{clients}->{$_}->{name} && $devAliases =~ /$hash->{clients}->{$_}->{name}:(.+?)(\s|$)/) || ($devAliases && defined $hash->{clients}->{$_}->{hostname} && $devAliases =~ /$hash->{clients}->{$_}->{hostname}:(.+?)(\s|$)/) || (defined $hash->{clients}->{$_}->{name} && $hash->{clients}->{$_}->{name} =~ /^([\w\.\-]+)$/) @@ -166,7 +154,7 @@ sub Unifi_Get($@) { elsif ($getName eq 'clientData' && $clients) { if($getVal && $getVal ne 'all') { # Make ID from Alias for (keys %{$hash->{clients}}) { - if ( $devAliases && $devAliases =~ /$_:$getVal/ + if ( ($devAliases && $devAliases =~ /$_:$getVal/) || ($devAliases && defined $hash->{clients}->{$_}->{name} && $devAliases =~ /$hash->{clients}->{$_}->{name}:$getVal/) || ($devAliases && defined $hash->{clients}->{$_}->{hostname} && $devAliases =~ /$hash->{clients}->{$_}->{hostname}:$getVal/) || (defined $hash->{clients}->{$_}->{name} && $hash->{clients}->{$_}->{name} eq $getVal) @@ -251,13 +239,17 @@ sub Unifi_DoUpdate($@) { } if (Unifi_CONNECTED($hash)) { + $hash->{unifi}->{updateStartTime} = time(); $hash->{updateDispatch} = { # {updateDispatch}->{callFn}[callFnRef,'receiveFn',receiveFnRef] Unifi_GetClients_Send => [\&Unifi_GetClients_Send,'Unifi_GetClients_Receive',\&Unifi_GetClients_Receive], - Unifi_GetAnother_Send => [\&Unifi_GetAnother_Send,'Unifi_GetAnother_Receive',\&Unifi_GetAnother_Receive], - Unifi_DoAfterUpdate => [\&Unifi_DoAfterUpdate,''], + Unifi_GetAccesspoints_Send => [\&Unifi_GetAccesspoints_Send,'Unifi_GetAccesspoints_Receive',\&Unifi_GetAccesspoints_Receive], + Unifi_GetWlans_Send => [\&Unifi_GetWlans_Send,'Unifi_GetWlans_Receive',\&Unifi_GetWlans_Receive], + Unifi_GetUnarchivedAlerts_Send => [\&Unifi_GetUnarchivedAlerts_Send,'Unifi_GetUnarchivedAlerts_Receive',\&Unifi_GetUnarchivedAlerts_Receive], + Unifi_GetEvents_Send => [\&Unifi_GetEvents_Send,'Unifi_GetEvents_Receive',\&Unifi_GetEvents_Receive], + Unifi_GetWlanGroups_Send => [\&Unifi_GetWlanGroups_Send,'Unifi_GetWlanGroups_Receive',\&Unifi_GetWlanGroups_Receive], + Unifi_ProcessUpdate => [\&Unifi_ProcessUpdate,''], }; Unifi_NextUpdateFn($hash,$self); - InternalTimer(time()+$hash->{interval}, 'Unifi_DoUpdate', $hash, 0); } else { Unifi_CONNECTED($hash,'disconnected'); @@ -272,7 +264,12 @@ sub Unifi_Login_Send($) { my ($name,$self) = ($hash->{NAME},Unifi_Whoami()); Log3 $name, 5, "$name ($self) - executed."; - HttpUtils_NonblockingGet($hash->{loginParams}); + HttpUtils_NonblockingGet( { + %{$hash->{httpParams}}, + url => $hash->{httpParams}->{loginUrl}, + data => $hash->{httpParams}->{loginData}, + callback => \&Unifi_Login_Receive + } ); return undef; } sub Unifi_Login_Receive($) { @@ -283,41 +280,34 @@ sub Unifi_Login_Receive($) { if ($err ne "") { Log3 $name, 5, "$name ($self) - Error while requesting ".$param->{url}." - $err"; } - elsif ($data ne "" && $hash->{version} == 3) { + elsif ($data ne "" && $hash->{unifi}->{version} == 3) { if ($data =~ /Invalid username or password/si) { Log3 $name, 1, "$name ($self) - Login Failed! Invalid username or password!"; } else { Log3 $name, 5, "$name ($self) - Login Failed! Version 3 should not deliver data on successfull login."; } } - elsif ($data ne "" || $hash->{version} == 3) { # v3 Login is empty if login is successfully - if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401 || ($hash->{version} == 3 && ($param->{code} == 302 || $param->{code} == 200))) { - if($data ne "") { - eval { - $data = decode_json($data); - 1; - } or do { - my $e = $@; - $data->{meta}->{rc} = 'error'; - $data->{meta}->{msg} = 'Unifi.FailedToDecodeJSON - $e'; - }; - } - if ($hash->{version} == 3 || $data->{meta}->{rc} eq "ok") { # v3 has no rc-state + elsif ($data ne "" || $hash->{unifi}->{version} == 3) { # v3 Login is empty if login is successfully + if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401 || ($hash->{unifi}->{version} == 3 && ($param->{code} == 302 || $param->{code} == 200))) { + eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; }; + + if ($hash->{unifi}->{version} == 3 || $data->{meta}->{rc} eq "ok") { # v3 has no rc-state Log3 $name, 5, "$name ($self) - state=ok || version=3"; - $param->{cookies} = ''; + $hash->{httpParams}->{header} = ''; for (split("\r\n",$param->{httpheader})) { if(/^Set-Cookie/) { s/Set-Cookie:\s(.*?);.*/Cookie: $1/; - $param->{cookies} .= $_.(($hash->{version} == 3) ? '' : '\r\n'); #v3 has only one cookie and no header at all + $hash->{httpParams}->{header} .= $_.'\r\n'; } } - - if($param->{cookies} ne '') { - Log3 $name, 5, "$name ($self) - Login successfully! $param->{cookies}"; + if($hash->{httpParams}->{header} ne '') { + $hash->{httpParams}->{header} =~ s/\\r\\n$//; + Log3 $name, 5, "$name ($self) - Login successfully! $hash->{httpParams}->{header}"; Unifi_CONNECTED($hash,'connected'); Unifi_DoUpdate($hash); return undef; } else { + $hash->{httpParams}->{header} = undef; Log3 $name, 5, "$name ($self) - Something went wrong, login seems ok but no cookies received."; } } @@ -336,7 +326,7 @@ sub Unifi_Login_Receive($) { } else { Log3 $name, 5, "$name ($self) - Login Failed (without msg)! - state:'$data->{meta}->{rc}'"; } - $param->{cookies} = ''; + $hash->{httpParams}->{header} = undef; } } else { Log3 $name, 5, "$name ($self) - Failed with HTTP Code $param->{code}!"; @@ -346,7 +336,7 @@ sub Unifi_Login_Receive($) { } Log3 $name, 5, "$name ($self) - Connect/Login to Unifi-Controller failed. Will try again after interval..."; Unifi_CONNECTED($hash,'disconnected'); - InternalTimer(time()+$hash->{interval}, 'Unifi_Login_Send', $hash, 0); + InternalTimer(time()+$hash->{unifi}->{interval}, 'Unifi_Login_Send', $hash, 0); return undef; } ############################################################################### @@ -356,13 +346,11 @@ sub Unifi_GetClients_Send($) { my ($name,$self) = ($hash->{NAME},Unifi_Whoami()); Log3 $name, 5, "$name ($self) - executed."; - my $param = { - %{$hash->{httpParams}}, - url => $hash->{url}."api/s/$hash->{siteID}/stat/sta", - header => ($hash->{version} == 3) ? $hash->{loginParams}->{cookies} : $hash->{loginParams}->{cookies}.$hash->{httpParams}->{header}, + HttpUtils_NonblockingGet( { + %{$hash->{httpParams}}, + url => $hash->{unifi}->{url}."stat/sta", callback => $hash->{updateDispatch}->{$self}[2] - }; - HttpUtils_NonblockingGet($param); + } ); return undef; } sub Unifi_GetClients_Receive($) { @@ -371,118 +359,281 @@ sub Unifi_GetClients_Receive($) { Log3 $name, 5, "$name ($self) - executed."; if ($err ne "") { - Log3 $name, 5, "$name ($self) - Error while requesting ".$param->{url}." - $err"; + Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"}); } elsif ($data ne "") { if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) { - eval { - $data = decode_json($data); - 1; - } or do { - my $e = $@; - $data->{meta}->{rc} = 'error'; - $data->{meta}->{msg} = 'Unifi.FailedToDecodeJSON - $e'; - }; + eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; }; + if ($data->{meta}->{rc} eq "ok") { Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'"; - readingsBeginUpdate($hash); - my $devAliases = AttrVal($name,"devAlias",0); - my $connectedClientIDs = {}; - my $i = 1; - my $clientName; + $hash->{unifi}->{connectedClients} = undef; for my $h (@{$data->{data}}) { - $clientName = $h->{user_id}; - if ( $devAliases && $devAliases =~ /$clientName:(.+?)(\s|$)/ - || ($devAliases && defined $h->{name} && $devAliases =~ /$h->{name}:(.+?)(\s|$)/) - || ($devAliases && defined $h->{hostname} && $devAliases =~ /$h->{hostname}:(.+?)(\s|$)/) - || (defined $h->{name} && $h->{name} =~ /^([\w\.\-]+)$/) - || (defined $h->{hostname} && $h->{hostname} =~ /^([\w\.\-]+)$/) - ) { - $clientName = $1; - } - - $hash->{clients}->{$h->{user_id}} = $h; - $connectedClientIDs->{$h->{user_id}} = 1; - readingsBulkUpdate($hash,$clientName."_hostname",(defined $h->{hostname}) ? $h->{hostname} : (defined $h->{ip}) ? $h->{ip} : 'Unknown'); - readingsBulkUpdate($hash,$clientName."_last_seen",strftime "%Y-%m-%d %H:%M:%S",localtime($h->{last_seen})); - readingsBulkUpdate($hash,$clientName."_uptime",$h->{uptime}); - readingsBulkUpdate($hash,$clientName,'connected'); - } - for my $clientID (keys %{$hash->{clients}}) { - if (!defined($connectedClientIDs->{$clientID}) && $hash->{READINGS}->{$clientID}->{VAL} ne 'disconnected') { - Log3 $name, 5, "$name ($self) - Client '$clientID' previously connected is now disconnected."; - if ( $devAliases && $devAliases =~ /$clientID:(.+?)(\s|$)/ - || ($devAliases && defined $hash->{clients}->{$clientID}->{name} && $devAliases =~ /$hash->{clients}->{$clientID}->{name}:(.+?)(\s|$)/) - || ($devAliases && defined $hash->{clients}->{$clientID}->{hostname} && $devAliases =~ /$hash->{clients}->{$clientID}->{hostname}:(.+?)(\s|$)/) - || (defined $hash->{clients}->{$clientID}->{name} && $hash->{clients}->{$clientID}->{name} =~ /^([\w\.\-]+)$/) - || (defined $hash->{clients}->{$clientID}->{hostname} && $hash->{clients}->{$clientID}->{hostname} =~ /^([\w\.\-]+)$/) - ) { - $clientID = $1; - } - readingsBulkUpdate($hash,$clientID,'disconnected') if($hash->{READINGS}->{$clientID}->{VAL} ne 'disconnected'); - } - } - readingsEndUpdate($hash,1); - } - else { - if (defined($data->{meta}->{msg})) { - if ($data->{meta}->{msg} eq 'api.err.LoginRequired') { - Log3 $name, 5, "$name ($self) - LoginRequired detected..."; - if(Unifi_CONNECTED($hash)) { - Log3 $name, 5, "$name ($self) - I am the first who detected LoginRequired. Do re-login..."; - Unifi_CONNECTED($hash,'disconnected'); - Unifi_DoUpdate($hash); - return undef; - } - } - elsif ($data->{meta}->{msg} eq "api.err.NoSiteContext" || ($hash->{version} == 3 && $data->{meta}->{msg} eq "api.err.InvalidObject")) { - Log3 $name, 1, "$name ($self) - Failed! - state:'$data->{meta}->{rc}' - msg:'$data->{meta}->{msg}'" - ." - This error indicates that the in your definition is wrong." - ." Try to modify your definition with = default."; - } - else { - Log3 $name, 5, "$name ($self) - Failed! - state:'$data->{meta}->{rc}' - msg:'$data->{meta}->{msg}'"; - } - } else { - Log3 $name, 5, "$name ($self) - Failed (without message)! - state:'$data->{meta}->{rc}'"; + $hash->{unifi}->{connectedClients}->{$h->{_id}} = 1; + $hash->{clients}->{$h->{_id}} = $h; } } - } - else { - Log3 $name, 5, "$name ($self) - Failed with HTTP Code $param->{code}."; + else { Unifi_ReceiveFailure($hash,$data->{meta}); } + } else { + Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."}); } } Unifi_NextUpdateFn($hash,$self); return undef; } ############################################################################### - -sub Unifi_GetAnother_Send($) { +sub Unifi_GetWlans_Send($) { my ($hash) = @_; my ($name,$self) = ($hash->{NAME},Unifi_Whoami()); Log3 $name, 5, "$name ($self) - executed."; - $hash->{updateDispatch}->{$self}[2]->( {hash => $hash} ); # DUMMY - #HttpUtils_NonblockingGet($param); + HttpUtils_NonblockingGet( { + %{$hash->{httpParams}}, + url => $hash->{unifi}->{url}."list/wlanconf", + callback => $hash->{updateDispatch}->{$self}[2], + } ); return undef; } -sub Unifi_GetAnother_Receive($) { +sub Unifi_GetWlans_Receive($) { my ($param, $err, $data) = @_; my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash}); - Log3 $hash->{NAME}, 5, "$hash->{NAME} ($self) - executed."; + Log3 $name, 5, "$name ($self) - executed."; - # Do + if ($err ne "") { + Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"}); + } + elsif ($data ne "") { + if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) { + eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; }; + + if ($data->{meta}->{rc} eq "ok") { + Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'"; + + for my $h (@{$data->{data}}) { + $hash->{wlans}->{$h->{_id}} = $h; + $hash->{wlans}->{$h->{_id}}->{x_passphrase} = '***'; # Don't show passphrase in list + } + } + else { Unifi_ReceiveFailure($hash,$data->{meta}); } + } else { + Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."}); + } + } + + Unifi_NextUpdateFn($hash,$self); + return undef; +} +############################################################################### +sub Unifi_GetWlanGroups_Send($) { + my ($hash) = @_; + my ($name,$self) = ($hash->{NAME},Unifi_Whoami()); + Log3 $name, 5, "$name ($self) - executed."; + + HttpUtils_NonblockingGet( { + %{$hash->{httpParams}}, + url => $hash->{unifi}->{url}."list/wlangroup", + callback => $hash->{updateDispatch}->{$self}[2], + } ); + return undef; +} +sub Unifi_GetWlanGroups_Receive($) { + my ($param, $err, $data) = @_; + my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash}); + Log3 $name, 5, "$name ($self) - executed."; + + if ($err ne "") { + Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"}); + } + elsif ($data ne "") { + if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) { + eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; }; + + if ($data->{meta}->{rc} eq "ok") { + Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'"; + + for my $h (@{$data->{data}}) { + $hash->{wlangroups}->{$h->{_id}} = $h; + } + } + else { Unifi_ReceiveFailure($hash,$data->{meta}); } + } else { + Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."}); + } + } + + Unifi_NextUpdateFn($hash,$self); + return undef; +} +############################################################################### +sub Unifi_GetUnarchivedAlerts_Send($) { + my ($hash) = @_; + my ($name,$self) = ($hash->{NAME},Unifi_Whoami()); + Log3 $name, 5, "$name ($self) - executed."; + + HttpUtils_NonblockingGet( { + %{$hash->{httpParams}}, + url => $hash->{unifi}->{url}."list/alarm", + callback => $hash->{updateDispatch}->{$self}[2], + data => "{'_sort': '-time', 'archived': False}", + } ); + return undef; +} +sub Unifi_GetUnarchivedAlerts_Receive($) { + my ($param, $err, $data) = @_; + my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash}); + Log3 $name, 5, "$name ($self) - executed."; + + if ($err ne "") { + Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"}); + } + elsif ($data ne "") { + if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) { + eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; }; + + if ($data->{meta}->{rc} eq "ok") { + Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'"; + + for my $h (@{$data->{data}}) { + $hash->{alerts_unarchived}->{$h->{_id}} = $h; + } + } + else { Unifi_ReceiveFailure($hash,$data->{meta}); } + } else { + Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."}); + } + } + + Unifi_NextUpdateFn($hash,$self); + return undef; +} +############################################################################### +sub Unifi_GetEvents_Send($) { + my ($hash) = @_; + my ($name,$self) = ($hash->{NAME},Unifi_Whoami()); + Log3 $name, 5, "$name ($self) - executed."; + + HttpUtils_NonblockingGet( { + %{$hash->{httpParams}}, + url => $hash->{unifi}->{url}."stat/event", + callback => $hash->{updateDispatch}->{$self}[2], + data => "{'within': 24}", # last 24 hours + } ); + return undef; +} +sub Unifi_GetEvents_Receive($) { + my ($param, $err, $data) = @_; + my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash}); + Log3 $name, 5, "$name ($self) - executed."; + + if ($err ne "") { + Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"}); + } + elsif ($data ne "") { + if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) { + eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; }; + + if ($data->{meta}->{rc} eq "ok") { + Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'"; + + for my $h (@{$data->{data}}) { + $hash->{events}->{$h->{_id}} = $h; + } + } + else { Unifi_ReceiveFailure($hash,$data->{meta}); } + } else { + Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."}); + } + } + + Unifi_NextUpdateFn($hash,$self); + return undef; +} +############################################################################### +sub Unifi_GetAccesspoints_Send($) { + my ($hash) = @_; + my ($name,$self) = ($hash->{NAME},Unifi_Whoami()); + Log3 $name, 5, "$name ($self) - executed."; + + HttpUtils_NonblockingGet( { + %{$hash->{httpParams}}, + url => $hash->{unifi}->{url}."stat/device", + callback => $hash->{updateDispatch}->{$self}[2], + data => "{'_depth': 2, 'test': 0}", + } ); + return undef; +} +sub Unifi_GetAccesspoints_Receive($) { + my ($param, $err, $data) = @_; + my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash}); + Log3 $name, 5, "$name ($self) - executed."; + + if ($err ne "") { + Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"}); + } + elsif ($data ne "") { + if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) { + eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; }; + + if ($data->{meta}->{rc} eq "ok") { + Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'"; + + for my $h (@{$data->{data}}) { + $hash->{accespoints}->{$h->{_id}} = $h; + } + } + else { Unifi_ReceiveFailure($hash,$data->{meta}); } + } else { + Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."}); + } + } Unifi_NextUpdateFn($hash,$self); return undef; } ############################################################################### -sub Unifi_DoAfterUpdate($) { +sub Unifi_ProcessUpdate($) { my ($hash) = @_; my ($name,$self) = ($hash->{NAME},Unifi_Whoami()); - Log3 $name, 5, "$name ($self) - executed."; + Log3 $name, 5, "$name ($self) - executed after ".sprintf('%.4f',time() - $hash->{unifi}->{updateStartTime})." seconds."; + + readingsBeginUpdate($hash); + #'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''# + + ### WLAN Client Readings + my ($clientName,$clientRef); + my $devAliases = AttrVal($name,"devAlias",0); + for my $clientID (keys %{$hash->{clients}}) { + $clientRef = $hash->{clients}->{$clientID}; + + if ( ($devAliases && $devAliases =~ /$clientID:(.+?)(\s|$)/) + || ($devAliases && defined $clientRef->{name} && $devAliases =~ /$clientRef->{name}:(.+?)(\s|$)/) + || ($devAliases && defined $clientRef->{hostname} && $devAliases =~ /$clientRef->{hostname}:(.+?)(\s|$)/) + || (defined $clientRef->{name} && $clientRef->{name} =~ /^([\w\.\-]+)$/) + || (defined $clientRef->{hostname} && $clientRef->{hostname} =~ /^([\w\.\-]+)$/) + ) { + $clientName = $1; + } else { $clientName = $clientID; } + + if (defined $hash->{unifi}->{connectedClients}->{$clientID}) { + readingsBulkUpdate($hash,$clientName."_hostname",(defined $clientRef->{hostname}) ? $clientRef->{hostname} : (defined $clientRef->{ip}) ? $clientRef->{ip} : 'Unknown'); + readingsBulkUpdate($hash,$clientName."_last_seen",strftime "%Y-%m-%d %H:%M:%S",localtime($clientRef->{last_seen})); + readingsBulkUpdate($hash,$clientName."_uptime",$clientRef->{uptime}); + readingsBulkUpdate($hash,$clientName,'connected'); + } + elsif (defined($hash->{READINGS}->{$clientName}) && $hash->{READINGS}->{$clientName}->{VAL} ne 'disconnected') { + Log3 $name, 5, "$name ($self) - Client '$clientName' previously connected is now disconnected."; + readingsBulkUpdate($hash,$clientName,'disconnected'); + } + } + ### Other... + + #'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''# + readingsEndUpdate($hash,1); + + Log3 $name, 5, "$name ($self) - finished after ".sprintf('%.4f',time() - $hash->{unifi}->{updateStartTime})." seconds."; + InternalTimer(time()+$hash->{unifi}->{interval}, 'Unifi_DoUpdate', $hash, 0); return undef; } @@ -495,36 +646,64 @@ sub Unifi_NextUpdateFn($$) { for (keys %{$hash->{updateDispatch}}) { # {updateDispatch}->{callFn}[callFnRef,'receiveFn',receiveFnRef] if($hash->{updateDispatch}->{$_}[1] && $hash->{updateDispatch}->{$_}[1] eq $fn) { delete $hash->{updateDispatch}->{$_}; - } elsif(!$NextUpdateFn && $hash->{updateDispatch}->{$_}[0] && $_ ne 'Unifi_DoAfterUpdate') { + } elsif(!$NextUpdateFn && $hash->{updateDispatch}->{$_}[0] && $_ ne 'Unifi_ProcessUpdate') { $NextUpdateFn = $hash->{updateDispatch}->{$_}[0]; } } - if (!$NextUpdateFn && $hash->{updateDispatch}->{Unifi_DoAfterUpdate}[0]) { - $NextUpdateFn = $hash->{updateDispatch}->{Unifi_DoAfterUpdate}[0]; - delete $hash->{updateDispatch}->{Unifi_DoAfterUpdate}; + if (!$NextUpdateFn && $hash->{updateDispatch}->{Unifi_ProcessUpdate}[0]) { + $NextUpdateFn = $hash->{updateDispatch}->{Unifi_ProcessUpdate}[0]; + delete $hash->{updateDispatch}->{Unifi_ProcessUpdate}; } $NextUpdateFn->($hash) if($NextUpdateFn); return undef; } ############################################################################### +sub Unifi_ReceiveFailure($$$) { + my ($hash,$meta) = @_; + my ($name,$self) = ($hash->{NAME},Unifi_Whowasi()); + + if (defined $meta->{msg}) { + if ($meta->{msg} eq 'api.err.LoginRequired') { + Log3 $name, 5, "$name ($self) - LoginRequired detected..."; + if(Unifi_CONNECTED($hash)) { + Log3 $name, 5, "$name ($self) - I am the first who detected LoginRequired. Do re-login..."; + Unifi_CONNECTED($hash,'disconnected'); + Unifi_DoUpdate($hash); + return undef; + } + } + elsif ($meta->{msg} eq "api.err.NoSiteContext" || ($hash->{unifi}->{version} == 3 && $meta->{msg} eq "api.err.InvalidObject")) { + Log3 $name, 1, "$name ($self) - Failed! - state:'$meta->{rc}' - msg:'$meta->{msg}'" + ." - This error indicates that the in your definition is wrong." + ." Try to modify your definition with = default."; + } + else { + Log3 $name, 5, "$name ($self) - Failed! - state:'$meta->{rc}' - msg:'$meta->{msg}'"; + } + } else { + Log3 $name, 5, "$name ($self) - Failed (without message)! - state:'$meta->{rc}'"; + } +} +############################################################################### + sub Unifi_CONNECTED($@) { my ($hash,$set) = @_; if ($set) { - $hash->{CONNECTED} = $set; + $hash->{unifi}->{CONNECTED} = $set; RemoveInternalTimer($hash); %{$hash->{updateDispatch}} = (); - if ($hash->{READINGS}->{state}->{VAL} ne $set) { + if (!defined($hash->{READINGS}->{state}->{VAL}) || $hash->{READINGS}->{state}->{VAL} ne $set) { readingsSingleUpdate($hash,"state",$set,1); } return undef; } else { - if ($hash->{CONNECTED} eq 'disabled') { + if ($hash->{unifi}->{CONNECTED} eq 'disabled') { return 'disabled'; } - elsif ($hash->{CONNECTED} eq 'connected') { + elsif ($hash->{unifi}->{CONNECTED} eq 'connected') { return 1; } else { return 0; @@ -533,24 +712,15 @@ sub Unifi_CONNECTED($@) { } ############################################################################### -sub Unifi_Urlencode($) { - my ($s) = @_; - $s =~ s/ /+/g; - $s =~ s/([^A-Za-z0-9\+-])/sprintf("%%%02X", ord($1))/seg; - return $s; -} -sub Unifi_Urldecode($) { - my ($s) = @_; - $s =~ s/\%([A-Fa-f0-9]{2})/pack('C', hex($1))/seg; - $s =~ s/\+/ /g; - return $s; -} sub Unifi_Whoami() { return (split('::',(caller(1))[3]))[1] || ''; } +sub Unifi_Whowasi() { return (split('::',(caller(2))[3]))[1] || ''; } ############################################################################### ### KNOWN RESPONSES ### # { "data" : [ ] , "meta" : { "msg" : "api.err.Invalid" , "rc" : "error"}} //Invalid Login credentials in v4, in v3 the login-html-page is returned # { "data" : [ ] , "meta" : { "rc" : "ok"}} +# "api.err.NoPermission" +# { "data" : [ ] , "meta" : { "msg" : "api.err.InvalidArgs" , "rc" : "error"}} //posted data is not ok # { "data" : [ ] , "meta" : { "msg" : "api.err.InvalidObject" , "rc" : "error"}} //Wrong siteID in v3 # { "data" : [ ] , "meta" : { "msg" : "api.err.NoSiteContext" , "rc" : "error"}} //Wrong siteID in v4 # { "data" : [ ] , "meta" : { "msg" : "api.err.LoginRequired" , "rc" : "error"}} //Login Required / cookie is invalid / While Login: Unifi v4 is used wiith controller v3 @@ -648,7 +818,7 @@ The device will be still connected, even it is in PowerSave-Mode. (In this mode

Get

    -
  • get <name> clientData <all|user_id|controllerAlias|hostname|devAlias>
    +
  • get <name> clientData <all|_id|controllerAlias|hostname|devAlias>
    Show more details about clients.
@@ -656,9 +826,9 @@ The device will be still connected, even it is in PowerSave-Mode. (In this mode

Attributes

  • attr devAlias
    - Can be used to rename device names in the format <user_id|controllerAlias|hostname>:Aliasname.
    + Can be used to rename device names in the format <_id|controllerAlias|hostname>:Aliasname.
    Separate using blank to rename multiple devices.
    - Example (user_id): attr unifi devAlias 5537d138e4b033c1832c5c84:iPhone-Claudiu
    + Example (_id): attr unifi devAlias 5537d138e4b033c1832c5c84:iPhone-Claudiu
    Example (controllerAlias): attr unifi devAlias iPhoneControllerAlias:iPhone-Claudiu
    Example (hostname): attr unifi devAlias iphone:iPhone-Claudiu