From aab2d16cc21ac095f4007ec90ffec867eed30e65 Mon Sep 17 00:00:00 2001 From: rapster <> Date: Mon, 24 Aug 2015 21:43:54 +0000 Subject: [PATCH] 74_Unifi: Added support for Controller v3 (v4 already supported). git-svn-id: https://svn.fhem.de/fhem/trunk@9130 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/74_Unifi.pm | 147 ++++++++++++++++++++++++++++-------------- 2 files changed, 100 insertions(+), 48 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 327630869..a8ea812bf 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # 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. + - feature: 74_Unifi: - Added support for Controller v3 (v4 already supported) - change: 74_Unifi: - changed get-Name from 'clientDetails' to 'clientData' - changed start from defFn to NotifyFn - added 'get clientData' selection by client-ID diff --git a/fhem/FHEM/74_Unifi.pm b/fhem/FHEM/74_Unifi.pm index a2e93e617..f9fb85054 100644 --- a/fhem/FHEM/74_Unifi.pm +++ b/fhem/FHEM/74_Unifi.pm @@ -1,5 +1,5 @@ ############################################################################### -# $Id: 74_Unifi.pm 2015-08-24 07:10 - rapster - rapster at x0e dot de $ +# $Id: 74_Unifi.pm 2015-08-24 23:00 - rapster - rapster at x0e dot de $ package main; use strict; @@ -42,28 +42,32 @@ sub Unifi_Define($$) { ); $hash->{httpParams} = { hash => $hash, - timeout => 5, + timeout => 8, method => "POST", noshutdown => 0, ignoreredirects => 1, loglevel => 5, sslargs => { SSL_verify_mode => 'SSL_VERIFY_NONE' }, - header => "Content-Type: application/json;charset=UTF-8" + header => ($hash->{version} == 3) ? undef : "Content-Type: application/json;charset=UTF-8" }; $hash->{loginParams} = { %{$hash->{httpParams}}, - url => $hash->{url}."api/login", - data => "{'username':'".$a[4]."', 'password':'".$a[5]."'}", 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]); + }else { + $hash->{loginParams}->{url} = $hash->{url}."api/login"; + $hash->{loginParams}->{data} = "{'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} = ''; readingsSingleUpdate($hash,"state","disconnected",1); } - + Log3 $name, 5, "$name: Defined with url:$hash->{url}, interval:$hash->{interval}, siteID:$hash->{siteID}, version:$hash->{version}"; return undef; } @@ -216,11 +220,8 @@ sub Unifi_DoUpdate($@) { } if ($hash->{STATE} ne 'connected') { - if($manual) { - Log3 $name, 3, "$name: DoUpdate - Manual Updates only allowed while connected, End now..."; - } else { - Unifi_Login_Send($hash) - } + readingsSingleUpdate($hash,"state","disconnected",1) if($hash->{STATE} ne 'disconnected'); + Unifi_Login_Send($hash) } else { Unifi_GetClients_Send($hash); # Do more... @@ -255,37 +256,63 @@ sub Unifi_Login_Receive($) { if ($err ne "") { Log3 $name, 5, "$name: Login_Receive - Error while requesting ".$param->{url}." - $err"; } - elsif ($data ne "") { - if ($param->{code} == 200 || $param->{code} == 400) { - eval { - $data = decode_json($data); - 1; - } or do { - my $e = $@; - Log3 $name, 5, "$name: Login_Receive - Failed to decode returned json object! Will try again after interval... - error:$e"; - - InternalTimer(time()+$hash->{interval}, 'Unifi_Login_Send', $hash, 0); - return undef; - }; - if ($data->{meta}->{rc} eq "ok") { - Log3 $name, 5, "$name: Login_Receive - Login successfully! - state:'$data->{meta}->{rc}'"; + elsif ($data ne "" && $hash->{version} == 3) { + if ($data =~ /Invalid username or password/si) { + Log3 $name, 1, "$name: Login_Receive - Login Failed! Invalid username or password! Will try again after interval... "; + } else { + Log3 $name, 5, "$name: Login_Receive - Login Failed! Version 3 should not deliver data on successfull login. Will try again after interval... "; + } + readingsSingleUpdate($hash,"state","disconnected",1) if($hash->{STATE} ne "disconnected"); + InternalTimer(time()+$hash->{interval}, 'Unifi_Login_Send', $hash, 0); + return undef; + } + 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($hash->{version} != 3) { + eval { + $data = decode_json($data); + 1; + } or do { + my $e = $@; + Log3 $name, 5, "$name: Login_Receive - Failed to decode returned json object! Will try again after interval... - error:$e"; + readingsSingleUpdate($hash,"state","disconnected",1) if($hash->{STATE} ne "disconnected"); + InternalTimer(time()+$hash->{interval}, 'Unifi_Login_Send', $hash, 0); + return undef; + }; + } + if ($hash->{version} == 3 || $data->{meta}->{rc} eq "ok") { # v3 has no rc-state + Log3 $name, 5, "$name: Login_Receive - state=ok || version=3"; $param->{cookies} = ''; for (split("\r\n",$param->{httpheader})) { if(/^Set-Cookie/) { s/Set-Cookie:\s(.*?);.*/Cookie: $1/; - $param->{cookies} .= $_.'\r\n'; + $param->{cookies} .= $_.(($hash->{version} == 3) ? '' : '\r\n'); #v3 has only one cookie and no header at all } } - Log3 $name, 5, "$name: Login_Receive - Received-cookies:$param->{cookies}"; - readingsSingleUpdate($hash,"state","connected",1); - Unifi_DoUpdate($hash); + if($param->{cookies} ne '') { + Log3 $name, 5, "$name: Login_Receive - Login successfully! $param->{cookies}"; + readingsSingleUpdate($hash,"state","connected",1); + RemoveInternalTimer($hash); + Unifi_DoUpdate($hash); + }else { + Log3 $name, 5, "$name: Login_Receive - Something went wrong, login seems ok but no cookies received. Will try again after interval..."; + readingsSingleUpdate($hash,"state","disconnected",1) if($hash->{STATE} ne "disconnected"); + InternalTimer(time()+$hash->{interval}, 'Unifi_Login_Send', $hash, 0); + } return undef; } else { if (defined($data->{meta}->{msg})) { - my $loglevel = ($data->{meta}->{msg} eq 'api.err.Invalid') ? 1 : 5; - Log3 $name, $loglevel, "$name: Login_Receive - Login Failed! - state:'$data->{meta}->{rc}' - msg:'$data->{meta}->{msg}'"; + if ($data->{meta}->{msg} eq 'api.err.Invalid') { + Log3 $name, 1, "$name: Login_Receive - Login Failed! Invalid username or password!" + ." - state:'$data->{meta}->{rc}' - msg:'$data->{meta}->{msg}' - Will try again after interval..."; + } elsif ($data->{meta}->{msg} eq 'api.err.LoginRequired') { + Log3 $name, 1, "$name: Login_Receive - Login Failed! This error while login indicates that you use wrong or" + ." have to define in your fhem definition. - Will try again after interval..."; + }else { + Log3 $name, 5, "$name: Login_Receive - Login Failed! - state:'$data->{meta}->{rc}' - msg:'$data->{meta}->{msg}'"; + } } else { Log3 $name, 5, "$name: Login_Receive - Login Failed (without message)! - state:'$data->{meta}->{rc}'"; } @@ -298,7 +325,7 @@ sub Unifi_Login_Receive($) { } } Log3 $name, 5, "$name: Login_Receive - Connect/Login to Unifi-Controller failed! Will try again after interval..."; - readingsSingleUpdate($hash,"state","disconnected",1) if($hash->{READINGS}->{state}->{VAL} ne "disconnected"); + readingsSingleUpdate($hash,"state","disconnected",1) if($hash->{STATE} ne "disconnected"); InternalTimer(time()+$hash->{interval}, 'Unifi_Login_Send', $hash, 0); return undef; } @@ -311,7 +338,7 @@ sub Unifi_GetClients_Send($) { my $param = { %{$hash->{httpParams}}, url => $hash->{url}."api/s/$hash->{siteID}/stat/sta", - header => $hash->{loginParams}->{cookies}.$hash->{httpParams}->{header}, + header => ($hash->{version} == 3) ? $hash->{loginParams}->{cookies} : $hash->{loginParams}->{cookies}.$hash->{httpParams}->{header}, callback => \&Unifi_GetClients_Receive }; HttpUtils_NonblockingGet($param); @@ -327,7 +354,7 @@ sub Unifi_GetClients_Receive($) { Log3 $name, 5, "$name: GetClients_Receive - Error while requesting ".$param->{url}." - $err"; } elsif ($data ne "") { - if ($param->{code} == 200 || $param->{code} == 401 || $param->{code} == 400) { + if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) { eval { $data = decode_json($data); 1; @@ -360,8 +387,7 @@ sub Unifi_GetClients_Receive($) { } else { if (defined($data->{meta}->{msg})) { - Log3 $name, 5, "$name: GetClients_Receive - Failed! - state:'$data->{meta}->{rc}' - msg:'$data->{meta}->{msg}'"; - if($data->{meta}->{msg} eq 'api.err.LoginRequired') { + if ($data->{meta}->{msg} eq 'api.err.LoginRequired') { Log3 $name, 5, "$name: GetClients_Receive - LoginRequired detected..."; if($hash->{STATE} ne 'disconnected') { Log3 $name, 5, "$name: GetClients_Receive - I am the first who detected LoginRequired. Do re-login..."; @@ -370,6 +396,14 @@ sub Unifi_GetClients_Receive($) { Unifi_DoUpdate($hash); } } + elsif ($data->{meta}->{msg} eq "api.err.NoSiteContext" || ($hash->{version} == 3 && $data->{meta}->{msg} eq "api.err.InvalidObject")) { + Log3 $name, 1, "$name: GetClients_Receive - 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. Will try again after interval..."; + } + else { + Log3 $name, 5, "$name: GetClients_Receive - Failed! - state:'$data->{meta}->{rc}' - msg:'$data->{meta}->{msg}'"; + } } else { Log3 $name, 5, "$name: GetClients_Receive - Failed (without message)! - state:'$data->{meta}->{rc}'"; } @@ -383,11 +417,26 @@ sub Unifi_GetClients_Receive($) { } ############################################################################### +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; +} +############################################################################### + ### KNOWN RESPONSES ### -# { "data" : [ ] , "meta" : { "msg" : "api.err.Invalid" , "rc" : "error"}} +# { "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"}} -# { "data" : [ ] , "meta" : { "msg" : "api.err.NoSiteContext" , "rc" : "error"}} -# { "data" : [ ] , "meta" : { "msg" : "api.err.LoginRequired" , "rc" : "error"}} +# { "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 / Unifi v4 is used wiith controller v3 ############################################################################### @@ -441,19 +490,22 @@ The device will be still connected, even it is in PowerSave-Mode. (In this mode [<interval>]:
    - optional: interval to fetch the information from the unifi-api.
    + (optional without <siteID> and <version>)
    + Interval to fetch the information from the unifi-api.
    default: 30 seconds

[<siteID>]:
    - optional: You can find the site-ID by selecting the site in the UniFi web interface.
    - e.g. (https://localhost:8443/manage/s/foobar) siteId = 'foobar'.
    - default: 'default'

    + (optional without <version>)
    + You can find the site-ID by selecting the site in the UniFi web interface.
    + e.g. https://192.168.12.13:8443/manage/s/foobar the siteId you must use is: foobar.
    + default: default

[<version>]:
    - optional: Your unifi-controller version.
    - This is not used at the moment, both v3.x and v4.x controller are supported.
    + (optional if you use unifi v4)
    + Unifi-controller version.
    + Version must be specified if version is not 4. At the moment version 3 and 4 are supported.
    default: 4



@@ -470,8 +522,7 @@ The device will be still connected, even it is in PowerSave-Mode. (In this mode

Set

  • set <name> update
    - Makes immediately a manual update.

  • - Note: Manual updates are only possible while unifi-controller is connected and device is not disabled. + Makes immediately a manual update.

  • set <name> clear <readings|clientData|all>