2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-01-31 18:59:33 +00:00

74_Unifi: changed start from defFn to NotifyFn with NOTIFYDEV=global

changed get-Name from 'clientDetails' to 'clientData' 
added 'get clientData' selection by client-ID 
added disable Attribute 
updated commandref 


git-svn-id: https://svn.fhem.de/fhem/trunk@9120 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
rapster 2015-08-23 16:53:57 +00:00
parent 2efd60341d
commit da97407271
2 changed files with 177 additions and 67 deletions

View File

@ -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

View File

@ -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: <version> 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
<code>optional: Your unifi-controller version.<br>
This is not used at the moment, both v3.x and v4.x controller are supported.<br>
default: 4</code><br>
</ul>
</ul> <br>
<br>
Notes:<br>
<li>If the login-cookie gets invalid (timeout or change of user-credentials / url), 'update with login' will be executed in next interval. </li>
<li>If you change &lt;interval&gt; while Unifi is running, the interval is changed only after the next automatic-update. <br>
To change it immediately, disable Unifi with "attr disable 1" and enable it again.</li>
</ul>
<h4>Example</h4>
<ul>
@ -368,31 +466,38 @@ The device will be still connected, even it is in PowerSave-Mode. (In this mode
<h4>Set</h4>
<ul>
<li><code>set &lt;name&gt; update</code><br>
Makes immediately a manual update. </li>
Makes immediately a manual update. </li><br>
Note: Manual updates are only possible while unifi-controller is connected and device is not disabled.
</ul><br>
<ul>
<li><code>set &lt;name&gt; clear &lt;readings|clientData|all&gt</code><br>
Clears the readings, clientData or both. </li>
Clears the readings, clientData or all. </li>
</ul>
<h4>Get</h4>
<ul>
<li><code>get &lt;name&gt; clientDetails</code><br>
Show more details about each client.</li>
<li><code>get &lt;name&gt; clientData &lt;all|clientID&gt</code><br>
Show more details about clients.</li>
</ul>
<h4>Attributes</h4>
<ul>
<li>attr disable &lt;1|0&gt;<br>
With this attribute you can disable the whole module. <br>
If set to 1 the module will be stopped and no updates are performed.<br>
If set to 0 the automatic updating will performed.</li>
<li>attr <a href="#verbose">verbose</a> 5<br>
Unifi itself has no attributes, but this attribute will help you if something does not work as espected.</li>
This attribute will help you if something does not work as espected.</li>
</ul>
<h4>Readings</h4>
<ul>
Note: All readings generate events. You can control this with <a href="#readingFnAttributes">these global attributes</a>.
<li>Each device has multiple readings.<br></li>
<li>The unifi-device reading 'state' represents the connections-state to the unifi-controller.</li>
<li>The unifi-device reading 'state' represents the connections-state to the unifi-controller.<br>
Possible states are 'connected', 'disconnected', 'initialized' and 'disabled'</li>
</ul>
<br>