diff --git a/fhem/CHANGED b/fhem/CHANGED index a09f07bec..327630869 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,10 @@ # 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. + - change: 74_Unifi: - changed get-Name from 'clientDetails' to 'clientData' + - changed start from defFn to NotifyFn + - added 'get clientData' selection by client-ID + - added disable Attribute + - updated commandref - added: 74_Unifi.pm for the Ubiquiti Networks (UBNT) - Unifi Controller. - change: 70_VolumeLink: - Changed vol/mute-RegexPattern modifier to /si - Changed default timeout to 0.5 diff --git a/fhem/FHEM/74_Unifi.pm b/fhem/FHEM/74_Unifi.pm index bc7a73805..f53c6a77d 100644 --- a/fhem/FHEM/74_Unifi.pm +++ b/fhem/FHEM/74_Unifi.pm @@ -1,5 +1,5 @@ ############################################################################### -# $Id: 74_Unifi.pm 2015-08-23 01:00 - rapster - rapster at x0e dot de $ +# $Id: 74_Unifi.pm 2015-08-23 18:00 - rapster - rapster at x0e dot de $ package main; use strict; @@ -11,11 +11,15 @@ use JSON qw(decode_json); sub Unifi_Initialize($$) { my ($hash) = @_; - $hash->{DefFn} = "Unifi_Define"; - $hash->{UndefFn} = "Unifi_Undef"; - $hash->{SetFn} = "Unifi_Set"; - $hash->{GetFn} = "Unifi_Get"; - $hash->{AttrList} = $readingFnAttributes; + $hash->{DefFn} = "Unifi_Define"; + $hash->{UndefFn} = "Unifi_Undef"; + $hash->{SetFn} = "Unifi_Set"; + $hash->{GetFn} = "Unifi_Get"; + $hash->{AttrFn} = 'Unifi_Attr'; + $hash->{NOTIFYDEV} = "global"; + $hash->{NotifyFn} = "Unifi_Notify"; + $hash->{AttrList} = "disable:1,0 " + .$readingFnAttributes; } ############################################################################### @@ -29,6 +33,7 @@ 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, url => "https://".$a[2].(($a[3] != 443) ? ':'.$a[3] : '').'/', interval => $a[6] || 30, @@ -49,16 +54,17 @@ sub Unifi_Define($$) { %{$hash->{httpParams}}, url => $hash->{url}."api/login", data => "{'username':'".$a[4]."', 'password':'".$a[5]."'}", - cookies => '', + cookies => ($hash->{loginParams}->{cookies}) ? $hash->{loginParams}->{cookies} : '', callback => \&Unifi_Login_Receive }; - readingsSingleUpdate($hash,"state","initialized",0); - Log3 $name, 5, "$name: Defined with url:$hash->{url}, interval:$hash->{interval}, siteID:$hash->{siteID}, version:$hash->{version}"; - - RemoveInternalTimer($hash); - Unifi_DoUpdate($hash); - + # 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; } ############################################################################### @@ -71,21 +77,49 @@ sub Unifi_Undef($$) { } ############################################################################### +sub Unifi_Notify($$) { + my ($hash,$dev) = @_; + my $name = $hash->{NAME}; + + return if($dev->{NAME} ne "global"); + return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}})); + + if(AttrVal($name, "disable", 0)) { + Log3 $name, 5, "$name: Notify - Detect disabled state, do nothing..."; + readingsSingleUpdate($hash,"state","disabled",0) if($hash->{STATE} ne "disabled"); + } else { + Log3 $name, 5, "$name: Notify - Call DoUpdate..."; + RemoveInternalTimer($hash); + readingsSingleUpdate($hash,"state","initialized",0); + Unifi_DoUpdate($hash); + } + return undef; +} +############################################################################### + sub Unifi_Set($@) { my ($hash,@a) = @_; return "\"set $hash->{NAME}\" needs at least an argument" if ( @a < 2 ); my ($name,$setName,$setVal) = @a; - if (AttrVal($name, "disable", 0)) { - Log3 $name, 5, "$name: set called with $setName but device is disabled" if ($setName ne "?"); + if (AttrVal($name, "disable", 0) && $setName !~ /clear/) { + if($setName eq "?") { + return "Unknown argument $setName, choose one of clear:all,readings,clientData"; + } else { + Log3 $name, 5, "$name: set called with $setName but device is disabled!"; + } return undef; } Log3 $name, 5, "$name: set called with $setName " . ($setVal ? $setVal : "") if ($setName ne "?"); - if($setName !~ /update|clear/) { + if($hash->{STATE} ne 'connected' && $setName !~ /clear/) { + return "Unknown argument $setName, choose one of clear:all,readings,clientData"; + } + elsif($setName !~ /update|clear/) { return "Unknown argument $setName, choose one of update:noArg clear:all,readings,clientData"; - } else { + } + else { Log3 $name, 4, "$name: set $setName"; if ($setName eq 'update') { @@ -98,7 +132,9 @@ sub Unifi_Set($@) { } } if ($setVal eq 'clientData' || $setVal eq 'all') { - undef $hash->{clients}; + for (keys %{$hash->{clients}}) { + delete $hash->{clients}->{$_}; + } } } } @@ -110,19 +146,60 @@ sub Unifi_Get($@) { my ($hash,@a) = @_; return "\"get $hash->{NAME}\" needs at least one argument" if ( @a < 2 ); my ($name,$getName,$getVal) = @a; + my $clients = join(',',keys(%{$hash->{clients}})); - if($getName !~ /clientDetails/) { - return "Unknown argument $getName, choose one of clientDetails:noArg"; + if($getName !~ /clientData/) { + return "Unknown argument $getName, choose one of ".(($clients) ? "clientData:all,$clients" : ""); } - elsif ($getName eq 'clientDetails') { - my $clientDetails = ''; - for my $client (sort keys %{$hash->{clients}}) { - for (sort keys %{$hash->{clients}->{$client}}) { - $clientDetails .= "$_ = $hash->{clients}->{$client}->{$_}\n"; + elsif ($getName eq 'clientData' && $clients) { + my $clientData = ''; + if(!$getVal || $getVal eq 'all') { + $clientData .= "======================================\n"; + for my $client (sort keys %{$hash->{clients}}) { + for (sort keys %{$hash->{clients}->{$client}}) { + $clientData .= "$_ = $hash->{clients}->{$client}->{$_}\n"; + } + $clientData .= "======================================\n"; } - $clientDetails .= "============================================\n"; + return $clientData; + } + elsif(defined($hash->{clients}->{$getVal})) { + $clientData .= "======================================\n"; + for (sort keys %{$hash->{clients}->{$getVal}}) { + $clientData .= "$_ = $hash->{clients}->{$getVal}->{$_}\n"; + } + $clientData .= "======================================\n"; + return $clientData; + } + else { + return "$hash->{NAME}: Unknown client '$getVal' in command '$getName', choose one of: $clients"; + } + } + return undef; +} +############################################################################### + +sub Unifi_Attr(@) { + my ($cmd,$name,$attr_name,$attr_value) = @_; + my $hash = $defs{$name}; + + if($cmd eq "set") { + if($attr_name eq "disable") { + if($attr_value == 1) { + readingsSingleUpdate($hash,"state","disabled",1); + RemoveInternalTimer($hash); + } + elsif($attr_value == 0 && $hash->{STATE} eq "disabled") { + readingsSingleUpdate($hash,"state","initialized",1); + Unifi_DoUpdate($hash); + } + } + } + elsif($cmd eq "del") { + if($attr_name eq "disable" && $hash->{STATE} eq "disabled") { + readingsSingleUpdate($hash,"state","initialized",1); + Unifi_DoUpdate($hash); } - return $clientDetails if($clientDetails ne ''); } return undef; } @@ -133,12 +210,22 @@ sub Unifi_DoUpdate($@) { my $name = $hash->{NAME}; Log3 $name, 5, "$name: DoUpdate - executed."; - if ( $hash->{STATE} ne 'connected' ) { - Unifi_Login_Send($hash); + if($hash->{STATE} eq "disabled") { + Log3 $name, 5, "$name: DoUpdate - Detect disabled state, End now..."; + return undef; + } + + 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) + } } else { Unifi_GetClients_Send($hash); + # Do more... if($manual) { - Log3 $name, 5, "$name: DoUpdate - Manual updated executed."; + Log3 $name, 5, "$name: DoUpdate - This was a manual-updated."; } else { InternalTimer(time()+$hash->{interval}, 'Unifi_DoUpdate', $hash, 0); } @@ -149,14 +236,20 @@ sub Unifi_DoUpdate($@) { sub Unifi_Login_Send($) { my ($hash) = @_; - Log3 $hash->{NAME}, 5, "$hash->{NAME}: Login_Send - executed."; + my $name = $hash->{NAME}; + Log3 $name, 5, "$name: Login_Send - executed."; + if($hash->{STATE} eq "disabled") { + Log3 $name, 5, "$name: Login_Receive - Detect disabled state, End now..."; + return undef; + } HttpUtils_NonblockingGet($hash->{loginParams}); return undef; } sub Unifi_Login_Receive($) { my ($param, $err, $data) = @_; - my $name = $param->{hash}->{NAME}; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; Log3 $name, 5, "$name: Login_Receive - executed."; if ($err ne "") { @@ -171,12 +264,11 @@ sub Unifi_Login_Receive($) { my $e = $@; Log3 $name, 5, "$name: Login_Receive - Failed to decode returned json object! Will try again after interval... - error:$e"; - InternalTimer(time()+$param->{hash}->{interval}, 'Unifi_Login_Send', $param->{hash}, 0); + InternalTimer(time()+$hash->{interval}, 'Unifi_Login_Send', $hash, 0); return undef; }; - Log3 $name, 5, "$name: Login_Receive - state:'$data->{meta}->{rc}'"; if ($data->{meta}->{rc} eq "ok") { - Log3 $name, 5, "$name: Login_Receive - Login successfully!"; + Log3 $name, 5, "$name: Login_Receive - Login successfully! - state:'$data->{meta}->{rc}'"; $param->{cookies} = ''; for (split("\r\n",$param->{httpheader})) { if(/^Set-Cookie/) { @@ -185,29 +277,29 @@ sub Unifi_Login_Receive($) { } } Log3 $name, 5, "$name: Login_Receive - Received-cookies:$param->{cookies}"; - readingsSingleUpdate($param->{hash},"state","connected",1); - Unifi_DoUpdate($param->{hash}); + readingsSingleUpdate($hash,"state","connected",1); + Unifi_DoUpdate($hash); return undef; } else { - $param->{cookies} = ''; 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}'"; } else { - Log3 $name, 5, "$name: Login_Receive - Login Failed (without message)!"; + Log3 $name, 5, "$name: Login_Receive - Login Failed (without message)! - state:'$data->{meta}->{rc}'"; } - readingsSingleUpdate($param->{hash},"state","disconnected",1) if($param->{hash}->{READINGS}->{state}->{VAL} ne "disconnected"); + $param->{cookies} = ''; + readingsSingleUpdate($hash,"state","disconnected",1) if($hash->{READINGS}->{state}->{VAL} ne "disconnected"); } } else { - readingsSingleUpdate($param->{hash},"state","disconnected",1) if($param->{hash}->{READINGS}->{state}->{VAL} ne "disconnected"); Log3 $name, 5, "$name: Login_Receive - Failed with HTTP Code $param->{code}!"; + readingsSingleUpdate($hash,"state","disconnected",1) if($hash->{READINGS}->{state}->{VAL} ne "disconnected"); } } Log3 $name, 5, "$name: Login_Receive - Connect/Login to Unifi-Controller failed! Will try again after interval..."; - readingsSingleUpdate($param->{hash},"state","disconnected",1) if($param->{hash}->{READINGS}->{state}->{VAL} ne "disconnected"); - InternalTimer(time()+$param->{hash}->{interval}, 'Unifi_Login_Send', $param->{hash}, 0); + readingsSingleUpdate($hash,"state","disconnected",1) if($hash->{READINGS}->{state}->{VAL} ne "disconnected"); + InternalTimer(time()+$hash->{interval}, 'Unifi_Login_Send', $hash, 0); return undef; } ############################################################################### @@ -215,6 +307,7 @@ sub Unifi_Login_Receive($) { sub Unifi_GetClients_Send($) { my ($hash) = @_; Log3 $hash->{NAME}, 5, "$hash->{NAME}: GetClients_Send - executed."; + my $param = { %{$hash->{httpParams}}, url => $hash->{url}."api/s/$hash->{siteID}/stat/sta", @@ -226,7 +319,8 @@ sub Unifi_GetClients_Send($) { } sub Unifi_GetClients_Receive($) { my ($param, $err, $data) = @_; - my $name = $param->{hash}->{NAME}; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; Log3 $name, 5, "$name: GetClients_Receive - executed."; if ($err ne "") { @@ -242,40 +336,37 @@ sub Unifi_GetClients_Receive($) { Log3 $name, 5, "$name: GetClients_Receive - Failed to decode returned json object! - error:$e"; return undef; }; - Log3 $name, 5, "$name: GetClients_Receive - state:'$data->{meta}->{rc}'"; if ($data->{meta}->{rc} eq "ok") { - Log3 $name, 5, "$name: GetClients_Receive - Data received successfully!"; + Log3 $name, 5, "$name: GetClients_Receive - Data received successfully! - state:'$data->{meta}->{rc}'"; - readingsBeginUpdate($param->{hash}); + readingsBeginUpdate($hash); my $connectedClientIDs = {}; my $i = 1; for my $h (@{$data->{data}}) { - $param->{hash}->{clients}->{$h->{user_id}} = $h; + $hash->{clients}->{$h->{user_id}} = $h; $connectedClientIDs->{$h->{user_id}} = 1; - readingsBulkUpdate($param->{hash},$h->{user_id}."_hostname",$h->{hostname}); - readingsBulkUpdate($param->{hash},$h->{user_id}."_last_seen",strftime "%Y-%m-%d %H:%M:%S",localtime($h->{last_seen})); - readingsBulkUpdate($param->{hash},$h->{user_id}."_essid",$h->{essid}); - readingsBulkUpdate($param->{hash},$h->{user_id}."_ip",$h->{ip}); - readingsBulkUpdate($param->{hash},$h->{user_id}."_uptime",$h->{uptime}); - readingsBulkUpdate($param->{hash},$h->{user_id},'connected'); + readingsBulkUpdate($hash,$h->{user_id}."_hostname",$h->{hostname}); + readingsBulkUpdate($hash,$h->{user_id}."_last_seen",strftime "%Y-%m-%d %H:%M:%S",localtime($h->{last_seen})); + readingsBulkUpdate($hash,$h->{user_id}."_uptime",$h->{uptime}); + readingsBulkUpdate($hash,$h->{user_id},'connected'); } - for my $clientID (keys %{$param->{hash}->{clients}}) { - if (!defined($connectedClientIDs->{$clientID}) && $param->{hash}->{READINGS}->{$clientID}->{VAL} ne 'disconnected') { - readingsBulkUpdate($param->{hash},$clientID,'disconnected'); + for my $clientID (keys %{$hash->{clients}}) { + if (!defined($connectedClientIDs->{$clientID}) && $hash->{READINGS}->{$clientID}->{VAL} ne 'disconnected') { Log3 $name, 5, "$name: GetClients_Receive - Client '$clientID' previously connected is now disconnected."; + readingsBulkUpdate($hash,$clientID,'disconnected'); } } - readingsEndUpdate($param->{hash},1); + readingsEndUpdate($hash,1); } 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') { - readingsSingleUpdate($param->{hash},"state","disconnected",1) if($param->{hash}->{READINGS}->{state}->{VAL} ne "disconnected"); Log3 $name, 5, "$name: GetClients_Receive - LoginRequired detected. Set state to disconnected..."; + readingsSingleUpdate($hash,"state","disconnected",1) if($hash->{READINGS}->{state}->{VAL} ne "disconnected"); } } else { - Log3 $name, 5, "$name: GetClients_Receive - Failed (without message)!"; + Log3 $name, 5, "$name: GetClients_Receive - Failed (without message)! - state:'$data->{meta}->{rc}'"; } } } @@ -297,6 +388,7 @@ sub Unifi_GetClients_Receive($) { 1; + =pod =begin html @@ -358,7 +450,13 @@ The device will be still connected, even it is in PowerSave-Mode. (In this mode optional: Your unifi-controller version.
This is not used at the moment, both v3.x and v4.x controller are supported.
default: 4

- +
+
+ Notes:
+
  • If the login-cookie gets invalid (timeout or change of user-credentials / url), 'update with login' will be executed in next interval.
  • +
  • If you change <interval> while Unifi is running, the interval is changed only after the next automatic-update.
    + To change it immediately, disable Unifi with "attr disable 1" and enable it again.
  • +

    Example