74_Unifi.pm 63 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492
  1. ##############################################################################
  2. # $Id: 74_Unifi.pm 14391 2017-05-27 20:20:30Z justme1968 $
  3. package main;
  4. use strict;
  5. use warnings;
  6. use HttpUtils;
  7. use POSIX;
  8. use JSON qw(decode_json);
  9. ##############################################################################}
  10. ### Forward declarations ####################################################{
  11. sub Unifi_Initialize($$);
  12. sub Unifi_Define($$);
  13. sub Unifi_Undef($$);
  14. sub Unifi_Notify($$);
  15. sub Unifi_Set($@);
  16. sub Unifi_Get($@);
  17. sub Unifi_Attr(@);
  18. sub Unifi_DoUpdate($@);
  19. sub Unifi_Login_Send($);
  20. sub Unifi_Login_Receive($);
  21. sub Unifi_GetClients_Send($);
  22. sub Unifi_GetClients_Receive($);
  23. sub Unifi_GetWlans_Send($);
  24. sub Unifi_GetWlans_Receive($);
  25. sub Unifi_GetHealth_Send($);
  26. sub Unifi_GetHealth_Receive($);
  27. sub Unifi_GetWlanGroups_Send($);
  28. sub Unifi_GetWlanGroups_Receive($);
  29. sub Unifi_GetUnarchivedAlerts_Send($);
  30. sub Unifi_GetUnarchivedAlerts_Receive($);
  31. sub Unifi_GetEvents_Send($);
  32. sub Unifi_GetEvents_Receive($);
  33. sub Unifi_GetAccesspoints_Send($);
  34. sub Unifi_GetAccesspoints_Receive($);
  35. sub Unifi_ProcessUpdate($);
  36. sub Unifi_SetClientReadings($);
  37. sub Unifi_SetHealthReadings($);
  38. sub Unifi_SetAccesspointReadings($);
  39. sub Unifi_DisconnectClient_Send($@);
  40. sub Unifi_DisconnectClient_Receive($);
  41. sub Unifi_ApCmd_Send($$@);
  42. sub Unifi_ApCmd_Receive($);
  43. sub Unifi_ArchiveAlerts_Send($);
  44. sub Unifi_Cmd_Receive($);
  45. sub Unifi_ClientNames($@);
  46. sub Unifi_ApNames($@);
  47. sub Unifi_NextUpdateFn($$);
  48. sub Unifi_ReceiveFailure($$);
  49. sub Unifi_CONNECTED($@);
  50. sub Unifi_Whoami();
  51. sub Unifi_Whowasi();
  52. ##############################################################################}
  53. sub Unifi_Initialize($$) {
  54. my ($hash) = @_;
  55. $hash->{DefFn} = "Unifi_Define";
  56. $hash->{UndefFn} = "Unifi_Undef";
  57. $hash->{SetFn} = "Unifi_Set";
  58. $hash->{GetFn} = "Unifi_Get";
  59. $hash->{AttrFn} = 'Unifi_Attr';
  60. $hash->{NotifyFn} = "Unifi_Notify";
  61. $hash->{AttrList} = "disable:1,0 "
  62. ."devAlias "
  63. ."ignoreWiredClients:1,0 "
  64. ."ignoreWirelessClients:1,0 "
  65. ."httpLoglevel:1,2,3,4,5 "
  66. ."eventPeriod "
  67. .$readingFnAttributes;
  68. }
  69. ###############################################################################
  70. sub Unifi_Define($$) {
  71. my ($hash, $def) = @_;
  72. my @a = split("[ \t][ \t]*", $def);
  73. return "Wrong syntax: use define <name> Unifi <ip> <port> <username> <password> [<interval> [<siteID> [<version>]]]" if(int(@a) < 6);
  74. return "Wrong syntax: <port> is not a number!" if(!looks_like_number($a[3]));
  75. return "Wrong syntax: <interval> is not a number!" if($a[6] && !looks_like_number($a[6]));
  76. return "Wrong syntax: <interval> too small, must be at least 5" if($a[6] && $a[6] < 5);
  77. 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/));
  78. my $name = $a[0];
  79. %$hash = ( %$hash,
  80. NOTIFYDEV => 'global',
  81. unifi => {
  82. CONNECTED => 0,
  83. eventPeriod => int(AttrVal($name,"eventPeriod",24)),
  84. interval => $a[6] || 30,
  85. version => $a[8] || 4,
  86. url => "https://".$a[2].(($a[3] == 443) ? '' : ':'.$a[3]).'/api/s/'.(($a[7]) ? $a[7] : 'default').'/',
  87. },
  88. );
  89. $hash->{httpParams} = {
  90. hash => $hash,
  91. timeout => 5,
  92. method => "POST",
  93. noshutdown => 0,
  94. ignoreredirects => 1,
  95. loglevel => AttrVal($name,"httpLoglevel",5),
  96. sslargs => { SSL_verify_mode => 0 },
  97. };
  98. if($hash->{unifi}->{version} == 3) {
  99. ( $hash->{httpParams}->{loginUrl} = $hash->{unifi}->{url} ) =~ s/api\/s.+/login/;
  100. $hash->{httpParams}->{loginData} = "login=login&username=".$a[4]."&password=".$a[5];
  101. }else {
  102. ( $hash->{httpParams}->{loginUrl} = $hash->{unifi}->{url} ) =~ s/api\/s.+/api\/login/;
  103. $hash->{httpParams}->{loginData} = '{"username":"'.$a[4].'", "password":"'.$a[5].'"}';
  104. }
  105. Log3 $name, 5, "$name: Defined with url:$hash->{unifi}->{url}, interval:$hash->{unifi}->{interval}, version:$hash->{unifi}->{version}";
  106. return undef;
  107. }
  108. ###############################################################################
  109. sub Unifi_Undef($$) {
  110. my ($hash,$arg) = @_;
  111. RemoveInternalTimer($hash);
  112. return undef;
  113. }
  114. ###############################################################################
  115. sub Unifi_Notify($$) {
  116. my ($hash,$dev) = @_;
  117. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  118. return if($dev->{NAME} ne "global");
  119. return if(!grep(m/^DEFINED|MODIFIED|INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}}));
  120. if(AttrVal($name, "disable", 0)) {
  121. Log3 $name, 5, "$name ($self) - executed. - Device '$name' is disabled, do nothing...";
  122. Unifi_CONNECTED($hash,'disabled');
  123. } else {
  124. Log3 $name, 5, "$name ($self) - executed. - Remove all Timers & Call DoUpdate...";
  125. Unifi_CONNECTED($hash,'initialized');
  126. Unifi_DoUpdate($hash);
  127. }
  128. return undef;
  129. }
  130. ###############################################################################
  131. sub Unifi_Set($@) {
  132. my ($hash,@a) = @_;
  133. return "\"set $hash->{NAME}\" needs at least an argument" if ( @a < 2 );
  134. my ($name,$setName,$setVal,$setVal2,$setVal3) = @a;
  135. Log3 $name, 5, "$name: set called with $setName " . ($setVal ? $setVal : "") if ($setName ne "?");
  136. if(Unifi_CONNECTED($hash) eq 'disabled' && $setName !~ /clear/) {
  137. return "Unknown argument $setName, choose one of clear:all,readings,clientData";
  138. Log3 $name, 5, "$name: set called with $setName but device is disabled!" if($setName ne "?");
  139. return undef;
  140. }
  141. my $clientNames = Unifi_ClientNames($hash);
  142. my $apNames = Unifi_ApNames($hash);
  143. if($setName !~ /archiveAlerts|restartAP|setLocateAP|unsetLocateAP|disconnectClient|update|clear|poeMode/) {
  144. return "Unknown argument $setName, choose one of update:noArg "
  145. ."clear:all,readings,clientData "
  146. .((defined $hash->{alerts_unarchived}[0] && scalar @{$hash->{alerts_unarchived}}) ? "archiveAlerts:noArg " : "")
  147. .(($apNames && Unifi_CONNECTED($hash)) ? "restartAP:all,$apNames setLocateAP:all,$apNames unsetLocateAP:all,$apNames " : "")
  148. .(($clientNames && Unifi_CONNECTED($hash)) ? "disconnectClient:all,$clientNames " : "")
  149. ."poeMode";
  150. }
  151. else {
  152. Log3 $name, 4, "$name: set $setName";
  153. if (Unifi_CONNECTED($hash)) {
  154. if ($setName eq 'disconnectClient') {
  155. if ($setVal && $setVal ne 'all') {
  156. $setVal = Unifi_ClientNames($hash,$setVal,'makeID');
  157. if (defined $hash->{clients}->{$setVal}) {
  158. Unifi_DisconnectClient_Send($hash,$setVal);
  159. }
  160. else {
  161. return "$hash->{NAME}: Unknown client '$setVal' in command '$setName', choose one of: all,$clientNames";
  162. }
  163. }
  164. elsif (!$setVal || $setVal eq 'all') {
  165. Unifi_DisconnectClient_Send($hash,keys(%{$hash->{clients}}));
  166. }
  167. }
  168. elsif ($setName eq 'poeMode') {
  169. return "usage: $setName <name|mac|id> <port> <off|auto|passive|passthrough|restart>" if( !$setVal3 );
  170. my $apRef;
  171. for my $apID (keys %{$hash->{accespoints}}) {
  172. my $ap = $hash->{accespoints}->{$apID};
  173. next if( !$ap->{port_table} );
  174. next if( $ap->{type} ne 'usw' );
  175. next if( $setVal ne $ap->{mac} && $setVal ne $ap->{device_id} && $ap->{name} !~ $setVal );
  176. return "multiple switches found for $setVal" if( $apRef );
  177. $apRef = $ap;
  178. }
  179. return "no switch $setVal found" if( !$apRef );
  180. if( $setVal2 !~ m/\d+/ ) {
  181. for my $port (@{$apRef->{port_table}}) {
  182. next if( $port->{name} !~ $setVal2 );
  183. $setVal2 = $port->{port_idx};
  184. last;
  185. }
  186. }
  187. return "port musst be numeric" if( $setVal2 !~ m/\d+/ );
  188. return "port musst be in [1..". scalar @{$apRef->{port_table}} ."] " if( $setVal2 < 1 || $setVal2 > scalar @{$apRef->{port_table}} );
  189. return "switch '$apRef->{name}' has no port $setVal2" if( !defined(@{$apRef->{port_table}}[$setVal2-1] ) );
  190. return "port $setVal2 of switch '$apRef->{name}' is not poe capable" if( !@{$apRef->{port_table}}[$setVal2-1]->{port_poe} );
  191. my $port_overrides = $apRef->{port_overrides};
  192. my $idx;
  193. my $i = 0;
  194. for my $entry (@{$port_overrides}) {
  195. if( $entry->{port_idx} eq $setVal2 ) {
  196. $idx = $i;
  197. last;
  198. }
  199. ++$i;
  200. }
  201. if( !defined($idx) ) {
  202. push @{$port_overrides}, {port_idx => $setVal2+0};
  203. $idx = scalar @{$port_overrides};
  204. }
  205. if( $setVal3 eq 'off' ) {
  206. $port_overrides->[$idx]{poe_mode} = "off";
  207. Unifi_RestJson_Send($hash, $apRef->{device_id}, {port_overrides => $port_overrides });
  208. } elsif( $setVal3 eq 'auto' || $setVal3 eq 'poe+' ) {
  209. #return "port $setVal2 not auto poe capable" if( @{$apRef->{port_table}}[$setVal2-1]->{poe_caps} & 0x03 ) ;
  210. $port_overrides->[$idx]{poe_mode} = "auto";
  211. Unifi_RestJson_Send($hash, $apRef->{device_id}, {port_overrides => $port_overrides });
  212. } elsif( $setVal3 eq 'passive' ) {
  213. #return "port $setVal2 not passive poe capable" if( @{$apRef->{port_table}}[$setVal2-1]->{poe_caps} & 0x04 ) ;
  214. $port_overrides->[$idx]{poe_mode} = "pasv24";
  215. Unifi_RestJson_Send($hash, $apRef->{device_id}, {port_overrides => $port_overrides });
  216. } elsif( $setVal3 eq 'passthrough' ) {
  217. #return "port $setVal2 not passthrough poe capable" if( @{$apRef->{port_table}}[$setVal2-1]->{poe_caps} & 0x08 ) ;
  218. $port_overrides->[$idx]{poe_mode} = "passthrough";
  219. Unifi_RestJson_Send($hash, $apRef->{device_id}, {port_overrides => $port_overrides });
  220. } elsif( $setVal3 eq 'restsart' ) {
  221. Unifi_ApJson_Send($hash,{cmd => 'power-cycle', mac => $apRef->{mac}, port_idx => $setVal2+0});
  222. } else {
  223. return "unknwon poe mode $setVal3";
  224. }
  225. }
  226. elsif ($setName eq 'archiveAlerts' && defined $hash->{alerts_unarchived}[0]) {
  227. Unifi_ArchiveAlerts_Send($hash);
  228. undef @{$hash->{alerts_unarchived}};
  229. }
  230. elsif ($setName eq 'restartAP') {
  231. if ($setVal && $setVal ne 'all') {
  232. $setVal = Unifi_ApNames($hash,$setVal,'makeID');
  233. if (defined $hash->{accespoints}->{$setVal}) {
  234. Unifi_ApCmd_Send($hash,'restart',$setVal);
  235. }
  236. else {
  237. return "$hash->{NAME}: Unknown accesspoint '$setVal' in command '$setName', choose one of: all,$apNames";
  238. }
  239. }
  240. elsif (!$setVal || $setVal eq 'all') {
  241. Unifi_ApCmd_Send($hash,'restart',keys(%{$hash->{accespoints}}));
  242. }
  243. }
  244. elsif ($setName eq 'setLocateAP') {
  245. if ($setVal && $setVal ne 'all') {
  246. $setVal = Unifi_ApNames($hash,$setVal,'makeID');
  247. if (defined $hash->{accespoints}->{$setVal}) {
  248. Unifi_ApCmd_Send($hash,'set-locate',$setVal);
  249. }
  250. else {
  251. return "$hash->{NAME}: Unknown accesspoint '$setVal' in command '$setName', choose one of: all,$apNames";
  252. }
  253. }
  254. elsif (!$setVal || $setVal eq 'all') {
  255. Unifi_ApCmd_Send($hash,'set-locate',keys(%{$hash->{accespoints}}));
  256. }
  257. }
  258. elsif ($setName eq 'unsetLocateAP') {
  259. if ($setVal && $setVal ne 'all') {
  260. $setVal = Unifi_ApNames($hash,$setVal,'makeID');
  261. if (defined $hash->{accespoints}->{$setVal}) {
  262. Unifi_ApCmd_Send($hash,'unset-locate',$setVal);
  263. }
  264. else {
  265. return "$hash->{NAME}: Unknown accesspoint '$setVal' in command '$setName', choose one of: all,$apNames";
  266. }
  267. }
  268. elsif (!$setVal || $setVal eq 'all') {
  269. Unifi_ApCmd_Send($hash,'unset-locate',keys(%{$hash->{accespoints}}));
  270. }
  271. }
  272. }
  273. if ($setName eq 'update') {
  274. RemoveInternalTimer($hash);
  275. Unifi_DoUpdate($hash,1);
  276. }
  277. elsif ($setName eq 'clear') {
  278. if ($setVal eq 'readings' || $setVal eq 'all') {
  279. for (keys %{$hash->{READINGS}}) {
  280. delete $hash->{READINGS}->{$_} if($_ ne 'state');
  281. }
  282. }
  283. if ($setVal eq 'clientData' || $setVal eq 'all') {
  284. %{$hash->{clients}} = ();
  285. }
  286. }
  287. }
  288. return undef;
  289. }
  290. ###############################################################################
  291. sub Unifi_Get($@) {
  292. my ($hash,@a) = @_;
  293. return "\"get $hash->{NAME}\" needs at least one argument" if ( @a < 2 );
  294. my ($name,$getName,$getVal) = @a;
  295. my $clientNames = Unifi_ClientNames($hash);
  296. if($getName !~ /events|clientData|unarchivedAlerts|poeState/) {
  297. return "Unknown argument $getName, choose one of "
  298. .((defined $hash->{events}[0] && scalar @{$hash->{events}}) ? "events:noArg " : "")
  299. .((defined $hash->{alerts_unarchived}[0] && scalar @{$hash->{alerts_unarchived}}) ? "unarchivedAlerts:noArg " : "")
  300. .(($clientNames) ? "clientData:all,$clientNames " : "")
  301. ."poeState";
  302. }
  303. elsif ($getName eq 'poeState') {
  304. my $poeState;
  305. for my $apID (keys %{$hash->{accespoints}}) {
  306. my $apRef = $hash->{accespoints}->{$apID};
  307. next if( $apRef->{type} ne 'usw' );
  308. next if( !$apRef->{port_table} );
  309. next if( $getVal && $getVal ne $apRef->{mac} && $getVal ne $apRef->{device_id} && $apRef->{name} !~ $getVal );
  310. $poeState .= "\n" if( $poeState );
  311. $poeState .= sprintf( "%-20s (mac:%-17s, id:%s)\n", $apRef->{name}, $apRef->{mac}, $apRef->{device_id} );
  312. $poeState .= sprintf( " %2s %-15s", "id", "name" );
  313. $poeState .= sprintf( " %s %s %-6s %-4s %-10s", "", "on", "mode", "", "class" );
  314. $poeState .= "\n";
  315. for my $port (@{$apRef->{port_table}}) {
  316. #next if( !$port->{port_poe} );
  317. $poeState .= sprintf( " %2i %-15s", $port->{port_idx}, $port->{name} );
  318. $poeState .= sprintf( " %s %s %-6s %-4s %-10s", $port->{poe_caps}, $port->{poe_enable}, $port->{poe_mode}, defined($port->{poe_good})?($port->{poe_good}?"good":""):"", defined($port->{poe_class})?$port->{poe_class}:"" ) if( $port->{port_poe} );
  319. $poeState .= sprintf( " %5.2fW %5.2fV %5.2fmA", $port->{poe_power}?$port->{poe_power}:0, $port->{poe_voltage}, $port->{poe_current}?$port->{poe_current}:0 ) if( $port->{port_poe} );
  320. $poeState .= "\n";
  321. }
  322. }
  323. $poeState = "====================================================\n". $poeState;
  324. $poeState .= "====================================================\n";
  325. return $poeState;
  326. }
  327. elsif ($getName eq 'unarchivedAlerts' && defined $hash->{alerts_unarchived}[0] && scalar @{$hash->{alerts_unarchived}}) {
  328. my $alerts = "====================================================\n";
  329. for my $alert (@{$hash->{alerts_unarchived}}) {
  330. for (sort keys %{$alert}) {
  331. if ($_ !~ /^(archived|_id|handled_admin_id|site_id|datetime|handled_time)$/) {
  332. $alert->{$_} = strftime "%Y-%m-%d %H:%M:%S",localtime($alert->{$_} / 1000) if($_ eq 'time');
  333. $alerts .= "$_ = ".((defined $alert->{$_}) ? $alert->{$_} : '')."\n";
  334. }
  335. }
  336. $alerts .= "====================================================\n";
  337. }
  338. return $alerts;
  339. }
  340. elsif ($getName eq 'events' && defined $hash->{events}[0] && scalar @{$hash->{events}}) {
  341. my $events = "==================================================================\n";
  342. for my $event (@{$hash->{events}}) {
  343. for (sort keys %{$event}) {
  344. if ($_ !~ /^(_id|site_id|subsystem|datetime|is_admin)$/) {
  345. $event->{$_} = strftime "%Y-%m-%d %H:%M:%S",localtime($event->{$_} / 1000) if($_ eq 'time');
  346. $events .= "$_ = ".((defined $event->{$_}) ? $event->{$_} : '')."\n";
  347. }
  348. }
  349. $events .= "==================================================================\n";
  350. }
  351. return $events;
  352. }
  353. elsif ($getName eq 'clientData' && $clientNames) {
  354. my $clientData = '';
  355. if ($getVal && $getVal ne 'all') {
  356. $getVal = Unifi_ClientNames($hash,$getVal,'makeID');
  357. }
  358. if (!$getVal || $getVal eq 'all') {
  359. $clientData .= "======================================\n";
  360. for my $client (sort keys %{$hash->{clients}}) {
  361. for (sort keys %{$hash->{clients}->{$client}}) {
  362. $clientData .= "$_ = ".((defined($hash->{clients}->{$client}->{$_})) ? $hash->{clients}->{$client}->{$_} : '')."\n";
  363. }
  364. $clientData .= "======================================\n";
  365. }
  366. return $clientData;
  367. }
  368. elsif(defined($hash->{clients}->{$getVal})) {
  369. $clientData .= "======================================\n";
  370. for (sort keys %{$hash->{clients}->{$getVal}}) {
  371. $clientData .= "$_ = ".((defined($hash->{clients}->{$getVal}->{$_})) ? $hash->{clients}->{$getVal}->{$_} : '')."\n";
  372. }
  373. $clientData .= "======================================\n";
  374. return $clientData;
  375. }
  376. else {
  377. return "$hash->{NAME}: Unknown client '$getVal' in command '$getName', choose one of: all,$clientNames";
  378. }
  379. }
  380. return undef;
  381. }
  382. ###############################################################################
  383. sub Unifi_Attr(@) {
  384. my ($cmd,$name,$attr_name,$attr_value) = @_;
  385. my $hash = $defs{$name};
  386. if($cmd eq "set") {
  387. if($attr_name eq "disable") {
  388. if($attr_value == 1) {
  389. Unifi_CONNECTED($hash,'disabled');
  390. }
  391. elsif($attr_value == 0 && Unifi_CONNECTED($hash) eq "disabled") {
  392. Unifi_CONNECTED($hash,'initialized');
  393. Unifi_DoUpdate($hash);
  394. }
  395. }
  396. elsif($attr_name eq "devAlias") {
  397. if (!$attr_value) {
  398. CommandDeleteAttr(undef, $name.' '.$attr_name);
  399. return 1;
  400. }
  401. elsif ($attr_value !~ /^([\w\.\-]+:[\w\.\-]+\s?)+$/) {
  402. return "$name: Value \"$attr_value\" is not allowed for devAlias!\n"
  403. ."Must be \"<ID>:<ALIAS> <ID2>:<ALIAS2>\", e.g. 123abc:MyIphone\n"
  404. ."Only these characters are allowed: [alphanumeric - _ .]";
  405. }
  406. }
  407. elsif($attr_name eq "httpLoglevel") {
  408. $hash->{httpParams}->{loglevel} = $attr_value;
  409. }
  410. elsif($attr_name eq "eventPeriod") {
  411. if (!looks_like_number($attr_value) || int($attr_value) < 1 || int($attr_value) > 168) {
  412. return "$name: Value \"$attr_value\" is not allowed.\n"
  413. ."eventPeriod must be a number between 1 and 168."
  414. }
  415. $hash->{unifi}->{eventPeriod} = int($attr_value);
  416. }
  417. }
  418. elsif($cmd eq "del") {
  419. if($attr_name eq "disable" && Unifi_CONNECTED($hash) eq "disabled") {
  420. Unifi_CONNECTED($hash,'initialized');
  421. Unifi_DoUpdate($hash);
  422. }
  423. elsif($attr_name eq "httpLoglevel") {
  424. $hash->{httpParams}->{loglevel} = 5;
  425. }
  426. elsif($attr_name eq "eventPeriod") {
  427. $hash->{unifi}->{eventPeriod} = 24;
  428. }
  429. }
  430. return undef;
  431. }
  432. ###############################################################################
  433. sub Unifi_DoUpdate($@) {
  434. my ($hash,$manual) = @_;
  435. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  436. Log3 $name, 5, "$name ($self) - executed.";
  437. if (Unifi_CONNECTED($hash) eq "disabled") {
  438. Log3 $name, 5, "$name ($self) - Device '$name' is disabled, End now...";
  439. return undef;
  440. }
  441. if (Unifi_CONNECTED($hash)) {
  442. $hash->{unifi}->{updateStartTime} = time();
  443. $hash->{updateDispatch} = { # {updateDispatch}->{callFn}[callFnRef,'receiveFn',receiveFnRef]
  444. Unifi_GetClients_Send => [\&Unifi_GetClients_Send,'Unifi_GetClients_Receive',\&Unifi_GetClients_Receive],
  445. Unifi_GetAccesspoints_Send => [\&Unifi_GetAccesspoints_Send,'Unifi_GetAccesspoints_Receive',\&Unifi_GetAccesspoints_Receive],
  446. Unifi_GetWlans_Send => [\&Unifi_GetWlans_Send,'Unifi_GetWlans_Receive',\&Unifi_GetWlans_Receive],
  447. Unifi_GetUnarchivedAlerts_Send => [\&Unifi_GetUnarchivedAlerts_Send,'Unifi_GetUnarchivedAlerts_Receive',\&Unifi_GetUnarchivedAlerts_Receive],
  448. Unifi_GetEvents_Send => [\&Unifi_GetEvents_Send,'Unifi_GetEvents_Receive',\&Unifi_GetEvents_Receive],
  449. # Unifi_GetWlanGroups_Send => [\&Unifi_GetWlanGroups_Send,'Unifi_GetWlanGroups_Receive',\&Unifi_GetWlanGroups_Receive],
  450. Unifi_GetHealth_Send => [\&Unifi_GetHealth_Send,'Unifi_GetHealth_Receive',\&Unifi_GetHealth_Receive],
  451. Unifi_ProcessUpdate => [\&Unifi_ProcessUpdate,''],
  452. };
  453. Unifi_NextUpdateFn($hash,$self);
  454. }
  455. else {
  456. Unifi_CONNECTED($hash,'disconnected');
  457. Unifi_Login_Send($hash)
  458. }
  459. return undef;
  460. }
  461. ###############################################################################
  462. sub Unifi_Login_Send($) {
  463. my ($hash) = @_;
  464. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  465. Log3 $name, 5, "$name ($self) - executed.";
  466. HttpUtils_NonblockingGet( {
  467. %{$hash->{httpParams}},
  468. url => $hash->{httpParams}->{loginUrl},
  469. data => $hash->{httpParams}->{loginData},
  470. callback => \&Unifi_Login_Receive
  471. } );
  472. return undef;
  473. }
  474. sub Unifi_Login_Receive($) {
  475. my ($param, $err, $data) = @_;
  476. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  477. Log3 $name, 5, "$name ($self) - executed.";
  478. if ($err ne "") {
  479. Log3 $name, 5, "$name ($self) - Error while requesting ".$param->{url}." - $err";
  480. }
  481. elsif ($data ne "" && $hash->{unifi}->{version} == 3) {
  482. if ($data =~ /Invalid username or password/si) {
  483. Log3 $name, 1, "$name ($self) - Login Failed! Invalid username or password!";
  484. } else {
  485. Log3 $name, 5, "$name ($self) - Login Failed! Version 3 should not deliver data on successfull login.";
  486. }
  487. }
  488. elsif ($data ne "" || $hash->{unifi}->{version} == 3) { # v3 Login is empty if login is successfully
  489. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401 || ($hash->{unifi}->{version} == 3 && ($param->{code} == 302 || $param->{code} == 200))) {
  490. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  491. if ($hash->{unifi}->{version} == 3 || $data->{meta}->{rc} eq "ok") { # v3 has no rc-state
  492. Log3 $name, 5, "$name ($self) - state=ok || version=3";
  493. $hash->{httpParams}->{header} = '';
  494. for (split("\r\n",$param->{httpheader})) {
  495. if(/^Set-Cookie/) {
  496. s/Set-Cookie:\s(.*?);.*/Cookie: $1/;
  497. $hash->{httpParams}->{header} .= $_.'\r\n';
  498. }
  499. }
  500. if($hash->{httpParams}->{header} ne '') {
  501. $hash->{httpParams}->{header} =~ s/\\r\\n$//;
  502. Log3 $name, 5, "$name ($self) - Login successfully! $hash->{httpParams}->{header}";
  503. Unifi_CONNECTED($hash,'connected');
  504. Unifi_DoUpdate($hash);
  505. return undef;
  506. } else {
  507. $hash->{httpParams}->{header} = undef;
  508. Log3 $name, 5, "$name ($self) - Something went wrong, login seems ok but no cookies received.";
  509. }
  510. }
  511. else {
  512. if (defined($data->{meta}->{msg})) {
  513. if ($data->{meta}->{msg} eq 'api.err.Invalid') {
  514. Log3 $name, 1, "$name ($self) - Login Failed! Invalid username or password!"
  515. ." - state:'$data->{meta}->{rc}' - msg:'$data->{meta}->{msg}'";
  516. } elsif ($data->{meta}->{msg} eq 'api.err.LoginRequired') {
  517. Log3 $name, 1, "$name ($self) - Login Failed! - state:'$data->{meta}->{rc}' - msg:'$data->{meta}->{msg}' -"
  518. ." This error while login indicates that you use wrong <version> or"
  519. ." have to define <version> in your fhem definition.";
  520. } else {
  521. Log3 $name, 5, "$name ($self) - Login Failed! - state:'$data->{meta}->{rc}' - msg:'$data->{meta}->{msg}'";
  522. }
  523. } else {
  524. Log3 $name, 5, "$name ($self) - Login Failed (without msg)! - state:'$data->{meta}->{rc}'";
  525. }
  526. $hash->{httpParams}->{header} = undef;
  527. }
  528. } else {
  529. Log3 $name, 5, "$name ($self) - Failed with HTTP Code $param->{code}!";
  530. }
  531. } else {
  532. Log3 $name, 5, "$name ($self) - Failed because no data was received!";
  533. }
  534. Log3 $name, 5, "$name ($self) - Connect/Login to Unifi-Controller failed. Will try again after interval...";
  535. Unifi_CONNECTED($hash,'disconnected');
  536. InternalTimer(time()+$hash->{unifi}->{interval}, 'Unifi_Login_Send', $hash, 0);
  537. return undef;
  538. }
  539. ###############################################################################
  540. sub Unifi_GetClients_Send($) {
  541. my ($hash) = @_;
  542. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  543. Log3 $name, 5, "$name ($self) - executed.";
  544. HttpUtils_NonblockingGet( {
  545. %{$hash->{httpParams}},
  546. method => "GET",
  547. url => $hash->{unifi}->{url}."stat/sta",
  548. callback => $hash->{updateDispatch}->{$self}[2]
  549. } );
  550. return undef;
  551. }
  552. sub Unifi_GetClients_Receive($) {
  553. my ($param, $err, $data) = @_;
  554. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  555. Log3 $name, 5, "$name ($self) - executed.";
  556. if ($err ne "") {
  557. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  558. }
  559. elsif ($data ne "") {
  560. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  561. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  562. if ($data->{meta}->{rc} eq "ok") {
  563. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  564. $hash->{unifi}->{connectedClients} = undef;
  565. for my $h (@{$data->{data}}) {
  566. $hash->{unifi}->{connectedClients}->{$h->{user_id}} = 1;
  567. $hash->{clients}->{$h->{user_id}} = $h;
  568. }
  569. }
  570. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  571. } else {
  572. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  573. }
  574. }
  575. Unifi_NextUpdateFn($hash,$self);
  576. return undef;
  577. }
  578. ###############################################################################
  579. sub Unifi_GetWlans_Send($) {
  580. my ($hash) = @_;
  581. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  582. Log3 $name, 5, "$name ($self) - executed.";
  583. HttpUtils_NonblockingGet( {
  584. %{$hash->{httpParams}},
  585. method => "GET",
  586. url => $hash->{unifi}->{url}."list/wlanconf",
  587. callback => $hash->{updateDispatch}->{$self}[2],
  588. } );
  589. return undef;
  590. }
  591. sub Unifi_GetWlans_Receive($) {
  592. my ($param, $err, $data) = @_;
  593. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  594. Log3 $name, 5, "$name ($self) - executed.";
  595. if ($err ne "") {
  596. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  597. }
  598. elsif ($data ne "") {
  599. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  600. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  601. if ($data->{meta}->{rc} eq "ok") {
  602. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  603. for my $h (@{$data->{data}}) {
  604. $hash->{wlans}->{$h->{_id}} = $h;
  605. $hash->{wlans}->{$h->{_id}}->{x_passphrase} = '***'; # Don't show passphrase in list
  606. }
  607. }
  608. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  609. } else {
  610. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  611. }
  612. }
  613. Unifi_NextUpdateFn($hash,$self);
  614. return undef;
  615. }
  616. ###############################################################################
  617. sub Unifi_GetHealth_Send($) {
  618. my ($hash) = @_;
  619. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  620. Log3 $name, 5, "$name ($self) - executed.";
  621. HttpUtils_NonblockingGet( {
  622. %{$hash->{httpParams}},
  623. method => "GET",
  624. url => $hash->{unifi}->{url}."stat/health",
  625. callback => $hash->{updateDispatch}->{$self}[2],
  626. } );
  627. return undef;
  628. }
  629. sub Unifi_GetHealth_Receive($) {
  630. my ($param, $err, $data) = @_;
  631. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  632. Log3 $name, 5, "$name ($self) - executed.";
  633. if ($err ne "") {
  634. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  635. }
  636. elsif ($data ne "") {
  637. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  638. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  639. if ($data->{meta}->{rc} eq "ok") {
  640. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  641. for my $h (@{$data->{data}}) {
  642. if (defined($h->{subsystem}) && $h->{subsystem} eq 'wlan') {
  643. $hash->{wlan_health} = $h;
  644. }
  645. }
  646. }
  647. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  648. } else {
  649. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  650. }
  651. }
  652. Unifi_NextUpdateFn($hash,$self);
  653. return undef;
  654. }
  655. ###############################################################################
  656. sub Unifi_GetWlanGroups_Send($) {
  657. my ($hash) = @_;
  658. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  659. Log3 $name, 5, "$name ($self) - executed.";
  660. HttpUtils_NonblockingGet( {
  661. %{$hash->{httpParams}},
  662. method => "GET",
  663. url => $hash->{unifi}->{url}."list/wlangroup",
  664. callback => $hash->{updateDispatch}->{$self}[2],
  665. } );
  666. return undef;
  667. }
  668. sub Unifi_GetWlanGroups_Receive($) {
  669. my ($param, $err, $data) = @_;
  670. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  671. Log3 $name, 5, "$name ($self) - executed.";
  672. if ($err ne "") {
  673. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  674. }
  675. elsif ($data ne "") {
  676. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  677. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  678. if ($data->{meta}->{rc} eq "ok") {
  679. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  680. for my $h (@{$data->{data}}) {
  681. $hash->{wlangroup}->{$h->{_id}} = $h;
  682. }
  683. }
  684. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  685. } else {
  686. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  687. }
  688. }
  689. Unifi_NextUpdateFn($hash,$self);
  690. return undef;
  691. }
  692. ###############################################################################
  693. sub Unifi_GetUnarchivedAlerts_Send($) {
  694. my ($hash) = @_;
  695. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  696. Log3 $name, 5, "$name ($self) - executed.";
  697. HttpUtils_NonblockingGet( {
  698. %{$hash->{httpParams}},
  699. url => $hash->{unifi}->{url}."list/alarm",
  700. callback => $hash->{updateDispatch}->{$self}[2],
  701. data => "{'_sort': '-time', 'archived': false}",
  702. } );
  703. return undef;
  704. }
  705. sub Unifi_GetUnarchivedAlerts_Receive($) {
  706. my ($param, $err, $data) = @_;
  707. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  708. Log3 $name, 5, "$name ($self) - executed.";
  709. if ($err ne "") {
  710. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  711. }
  712. elsif ($data ne "") {
  713. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  714. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  715. if ($data->{meta}->{rc} eq "ok") {
  716. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  717. $hash->{alerts_unarchived} = $data->{data}; #array
  718. }
  719. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  720. } else {
  721. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  722. }
  723. }
  724. Unifi_NextUpdateFn($hash,$self);
  725. return undef;
  726. }
  727. ###############################################################################
  728. sub Unifi_GetEvents_Send($) {
  729. my ($hash) = @_;
  730. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  731. Log3 $name, 5, "$name ($self) - executed.";
  732. HttpUtils_NonblockingGet( {
  733. %{$hash->{httpParams}},
  734. url => $hash->{unifi}->{url}."stat/event",
  735. callback => $hash->{updateDispatch}->{$self}[2],
  736. data => "{'_sort': '-time', 'within': ".$hash->{unifi}->{eventPeriod}."}", # last 24 hours
  737. } );
  738. return undef;
  739. }
  740. sub Unifi_GetEvents_Receive($) {
  741. my ($param, $err, $data) = @_;
  742. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  743. Log3 $name, 5, "$name ($self) - executed.";
  744. if ($err ne "") {
  745. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  746. }
  747. elsif ($data ne "") {
  748. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  749. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  750. if ($data->{meta}->{rc} eq "ok") {
  751. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  752. $hash->{events} = $data->{data}; #array
  753. }
  754. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  755. } else {
  756. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  757. }
  758. }
  759. Unifi_NextUpdateFn($hash,$self);
  760. return undef;
  761. }
  762. ###############################################################################
  763. sub Unifi_GetAccesspoints_Send($) {
  764. my ($hash) = @_;
  765. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  766. Log3 $name, 5, "$name ($self) - executed.";
  767. HttpUtils_NonblockingGet( {
  768. %{$hash->{httpParams}},
  769. url => $hash->{unifi}->{url}."stat/device",
  770. callback => $hash->{updateDispatch}->{$self}[2],
  771. data => "{'_depth': 2, 'test': 0}",
  772. } );
  773. return undef;
  774. }
  775. sub Unifi_GetAccesspoints_Receive($) {
  776. my ($param, $err, $data) = @_;
  777. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  778. Log3 $name, 5, "$name ($self) - executed.";
  779. if ($err ne "") {
  780. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  781. }
  782. elsif ($data ne "") {
  783. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  784. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  785. if ($data->{meta}->{rc} eq "ok") {
  786. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  787. for my $h (@{$data->{data}}) {
  788. $hash->{accespoints}->{$h->{_id}} = $h;
  789. }
  790. }
  791. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  792. } else {
  793. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  794. }
  795. }
  796. Unifi_NextUpdateFn($hash,$self);
  797. return undef;
  798. }
  799. ###############################################################################
  800. sub Unifi_ProcessUpdate($) {
  801. my ($hash) = @_;
  802. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  803. Log3 $name, 5, "$name ($self) - executed after ".sprintf('%.4f',time() - $hash->{unifi}->{updateStartTime})." seconds.";
  804. readingsBeginUpdate($hash);
  805. #'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''#
  806. Unifi_SetHealthReadings($hash);
  807. Unifi_SetClientReadings($hash);
  808. Unifi_SetAccesspointReadings($hash);
  809. ## WLANGROUPS ???
  810. #'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''#
  811. readingsEndUpdate($hash,1);
  812. Log3 $name, 5, "$name ($self) - finished after ".sprintf('%.4f',time() - $hash->{unifi}->{updateStartTime})." seconds.";
  813. InternalTimer(time()+$hash->{unifi}->{interval}, 'Unifi_DoUpdate', $hash, 0);
  814. return undef;
  815. }
  816. ###############################################################################
  817. sub Unifi_SetClientReadings($) {
  818. my ($hash) = @_;
  819. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  820. Log3 $name, 5, "$name ($self) - executed.";
  821. my $apNames = {};
  822. for my $apID (keys %{$hash->{accespoints}}) {
  823. my $apRef = $hash->{accespoints}->{$apID};
  824. $apNames->{$apRef->{mac}} = $apRef->{name} ? $apRef->{name} : $apRef->{ip};
  825. }
  826. my $ignoreWired = AttrVal($name,"ignoreWiredClients",undef);
  827. my $ignoreWireless = AttrVal($name,"ignoreWirelessClients",undef);
  828. my ($apName,$clientName,$clientRef);
  829. for my $clientID (keys %{$hash->{clients}}) {
  830. $clientRef = $hash->{clients}->{$clientID};
  831. $clientName = Unifi_ClientNames($hash,$clientID,'makeAlias');
  832. next if( $ignoreWired && $clientRef->{is_wired} );
  833. next if( $ignoreWireless && !$clientRef->{is_wired} );
  834. $apName = "unknown";
  835. if ($clientRef->{is_wired}
  836. && defined $clientRef->{sw_mac} && defined($apNames->{$clientRef->{sw_mac}}) ) {
  837. $apName = $apNames->{$clientRef->{sw_mac}};
  838. } elsif (defined $clientRef->{ap_mac} && defined($apNames->{$clientRef->{ap_mac}}) ) {
  839. $apName = $apNames->{$clientRef->{ap_mac}};
  840. }
  841. if (defined $hash->{unifi}->{connectedClients}->{$clientID}) {
  842. readingsBulkUpdate($hash,$clientName."_hostname",(defined $clientRef->{hostname}) ? $clientRef->{hostname} : (defined $clientRef->{ip}) ? $clientRef->{ip} : 'Unknown');
  843. readingsBulkUpdate($hash,$clientName."_last_seen",strftime "%Y-%m-%d %H:%M:%S",localtime($clientRef->{last_seen}));
  844. readingsBulkUpdate($hash,$clientName."_uptime",$clientRef->{uptime});
  845. readingsBulkUpdate($hash,$clientName."_snr",$clientRef->{rssi});
  846. readingsBulkUpdate($hash,$clientName."_accesspoint",$apName);
  847. readingsBulkUpdate($hash,$clientName,'connected');
  848. }
  849. elsif (defined($hash->{READINGS}->{$clientName}) && $hash->{READINGS}->{$clientName}->{VAL} ne 'disconnected') {
  850. Log3 $name, 5, "$name ($self) - Client '$clientName' previously connected is now disconnected.";
  851. readingsBulkUpdate($hash,$clientName,'disconnected');
  852. }
  853. }
  854. return undef;
  855. }
  856. ###############################################################################
  857. sub Unifi_SetHealthReadings($) {
  858. my ($hash) = @_;
  859. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  860. Log3 $name, 5, "$name ($self) - executed.";
  861. readingsBulkUpdate($hash,'-UC_wlan_state',$hash->{wlan_health}->{status});
  862. readingsBulkUpdate($hash,'-UC_wlan_users',$hash->{wlan_health}->{num_user});
  863. readingsBulkUpdate($hash,'-UC_wlan_accesspoints',$hash->{wlan_health}->{num_ap});
  864. readingsBulkUpdate($hash,'-UC_wlan_guests',$hash->{wlan_health}->{num_guest});
  865. readingsBulkUpdate($hash,'-UC_unarchived_alerts',scalar @{$hash->{alerts_unarchived}}) if(ref($hash->{alerts_unarchived}) eq 'ARRAY');
  866. readingsBulkUpdate($hash,'-UC_events',scalar(@{$hash->{events}}).' (last '.$hash->{unifi}->{eventPeriod}.'h)') if(ref($hash->{events}) eq 'ARRAY');
  867. return undef;
  868. }
  869. ###############################################################################
  870. sub Unifi_SetAccesspointReadings($) {
  871. my ($hash) = @_;
  872. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  873. Log3 $name, 5, "$name ($self) - executed.";
  874. my ($apName,$apRef,$essid);
  875. for my $apID (keys %{$hash->{accespoints}}) {
  876. $essid = '';
  877. $apRef = $hash->{accespoints}->{$apID};
  878. $apName = ($apRef->{name}) ? $apRef->{name} : $apRef->{ip};
  879. if (defined $apRef->{vap_table} && scalar @{$apRef->{vap_table}}) {
  880. for my $vap (@{$apRef->{vap_table}}) {
  881. $essid .= $vap->{essid}.',';
  882. }
  883. $essid =~ s/.$//;
  884. } else {
  885. my $essid = 'none';
  886. }
  887. readingsBulkUpdate($hash,'-AP_'.$apName.'_state',($apRef->{state} == 1) ? 'ok' : 'error');
  888. readingsBulkUpdate($hash,'-AP_'.$apName.'_clients',$apRef->{'num_sta'});
  889. if( $apRef->{type} eq 'uap' ) {
  890. readingsBulkUpdate($hash,'-AP_'.$apName.'_essid',$essid);
  891. readingsBulkUpdate($hash,'-AP_'.$apName.'_utilizationNA',$apRef->{'na_cu_total'}) if( defined($apRef->{'na_cu_total'}) );
  892. readingsBulkUpdate($hash,'-AP_'.$apName.'_utilizationNG',$apRef->{'ng_cu_total'}) if( defined($apRef->{'ng_cu_total'}) );
  893. }
  894. readingsBulkUpdate($hash,'-AP_'.$apName.'_locate',(!defined $apRef->{locating}) ? 'unknown' : ($apRef->{locating}) ? 'on' : 'off');
  895. my $poe_power;
  896. for my $port (@{$apRef->{port_table}}) {
  897. next if( !$port->{port_poe} );
  898. $poe_power += $port->{poe_power} if( defined($port->{poe_power}) );
  899. }
  900. readingsBulkUpdate($hash,'-AP_'.$apName.'_poePower', $poe_power) if( defined($poe_power) );
  901. # readingsBulkUpdate($hash,'-AP_'.$apName.'_guests',$apRef->{'guest-num_sta'});
  902. # readingsBulkUpdate($hash,'-AP_'.$apName.'_users',$apRef->{'user-num_sta'});
  903. # readingsBulkUpdate($hash,'-AP_'.$apName.'_last_seen',$apRef->{'last_seen'});
  904. }
  905. return undef;
  906. }
  907. ###############################################################################
  908. sub Unifi_DisconnectClient_Send($@) {
  909. my ($hash,@clients) = @_;
  910. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  911. Log3 $name, 5, "$name ($self) - executed with count:'".scalar(@clients)."', ID:'".$clients[0]."'";
  912. my $id = shift @clients;
  913. HttpUtils_NonblockingGet( {
  914. %{$hash->{httpParams}},
  915. url => $hash->{unifi}->{url}."cmd/stamgr",
  916. callback => \&Unifi_DisconnectClient_Receive,
  917. clients => [@clients],
  918. data => "{'mac': '".$hash->{clients}->{$id}->{mac}."', 'cmd': 'kick-sta'}",
  919. } );
  920. return undef;
  921. }
  922. ###############################################################################
  923. sub Unifi_DisconnectClient_Receive($) {
  924. my ($param, $err, $data) = @_;
  925. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  926. Log3 $name, 5, "$name ($self) - executed.";
  927. if ($err ne "") {
  928. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  929. }
  930. elsif ($data ne "") {
  931. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  932. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  933. if ($data->{meta}->{rc} eq "ok") {
  934. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  935. }
  936. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  937. } else {
  938. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  939. }
  940. }
  941. if (scalar @{$param->{clients}}) {
  942. Unifi_DisconnectClient_Send($hash,@{$param->{clients}});
  943. }
  944. return undef;
  945. }
  946. ###############################################################################
  947. sub Unifi_ApCmd_Send($$@) { #cmd: 'set-locate', 'unset-locate', 'restart'
  948. my ($hash,$cmd,@aps) = @_;
  949. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  950. Log3 $name, 5, "$name ($self) - executed with cmd:'".$cmd."', count:'".scalar(@aps)."', ID:'".$aps[0]."'";
  951. my $id = shift @aps;
  952. HttpUtils_NonblockingGet( {
  953. %{$hash->{httpParams}},
  954. url => $hash->{unifi}->{url}."cmd/devmgr",
  955. callback => \&Unifi_ApCmd_Receive,
  956. aps => [@aps],
  957. cmd => $cmd,
  958. data => "{'mac': '".$hash->{accespoints}->{$id}->{mac}."', 'cmd': '".$cmd."'}",
  959. } );
  960. return undef;
  961. }
  962. sub Unifi_ApJson_Send($$) {
  963. my ($hash,$data) = @_;
  964. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  965. my $json = encode_json( $data );
  966. Log3 $name, 5, "$name ($self) - executed with $json.";
  967. HttpUtils_NonblockingGet( {
  968. %{$hash->{httpParams}},
  969. url => $hash->{unifi}->{url}."cmd/devmgr",
  970. callback => \&Unifi_ApCmd_Receive,
  971. aps => [],
  972. data => $json,
  973. } );
  974. return undef;
  975. }
  976. sub Unifi_RestJson_Send($$$) {
  977. my ($hash,$id,$data) = @_;
  978. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  979. my $json = encode_json( $data );
  980. Log3 $name, 5, "$name ($self) - executed with $json.";
  981. HttpUtils_NonblockingGet( {
  982. %{$hash->{httpParams}},
  983. method => "PUT",
  984. url => $hash->{unifi}->{url}."rest/device/".$id,
  985. callback => \&Unifi_ApCmd_Receive,
  986. aps => [],
  987. data => $json,
  988. } );
  989. return undef;
  990. }
  991. ###############################################################################
  992. sub Unifi_ApCmd_Receive($) {
  993. my ($param, $err, $data) = @_;
  994. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  995. Log3 $name, 5, "$name ($self) - executed.";
  996. if ($err ne "") {
  997. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  998. }
  999. elsif ($data ne "") {
  1000. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  1001. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  1002. if ($data->{meta}->{rc} eq "ok") {
  1003. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  1004. }
  1005. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  1006. } else {
  1007. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  1008. }
  1009. }
  1010. if (scalar @{$param->{aps}}) {
  1011. Unifi_ApCmd_Send($hash,$param->{cmd},@{$param->{aps}});
  1012. }
  1013. return undef;
  1014. }
  1015. ###############################################################################
  1016. sub Unifi_ArchiveAlerts_Send($) {
  1017. my ($hash) = @_;
  1018. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  1019. Log3 $name, 5, "$name ($self) - executed.";
  1020. HttpUtils_NonblockingGet( {
  1021. %{$hash->{httpParams}},
  1022. url => $hash->{unifi}->{url}."cmd/evtmgr",
  1023. callback => \&Unifi_Cmd_Receive,
  1024. data => "{'cmd': 'archive-all-alarms'}",
  1025. } );
  1026. return undef;
  1027. }
  1028. ###############################################################################
  1029. sub Unifi_Cmd_Receive($) {
  1030. my ($param, $err, $data) = @_;
  1031. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  1032. Log3 $name, 5, "$name ($self) - executed.";
  1033. if ($err ne "") {
  1034. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  1035. }
  1036. elsif ($data ne "") {
  1037. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  1038. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  1039. if ($data->{meta}->{rc} eq "ok") {
  1040. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  1041. }
  1042. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  1043. } else {
  1044. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  1045. }
  1046. }
  1047. return undef;
  1048. }
  1049. ###############################################################################
  1050. sub Unifi_ClientNames($@) {
  1051. my ($hash,$ID,$W) = @_;
  1052. my $clientRef;
  1053. my $devAliases = AttrVal($hash->{NAME},"devAlias",0);
  1054. if(defined $ID && defined $W && $W eq 'makeAlias') { # Return Alias from ID
  1055. $clientRef = $hash->{clients}->{$ID};
  1056. if ( ($devAliases && $devAliases =~ /$ID:(.+?)(\s|$)/)
  1057. || ($devAliases && defined $clientRef->{name} && $devAliases =~ /$clientRef->{name}:(.+?)(\s|$)/)
  1058. || ($devAliases && defined $clientRef->{hostname} && $devAliases =~ /$clientRef->{hostname}:(.+?)(\s|$)/)
  1059. || (defined $clientRef->{name} && $clientRef->{name} =~ /^([\w\.\-]+)$/)
  1060. || (defined $clientRef->{hostname} && $clientRef->{hostname} =~ /^([\w\.\-]+)$/)
  1061. ) {
  1062. $ID = $1;
  1063. }
  1064. return $ID;
  1065. }
  1066. elsif (defined $ID && defined $W && $W eq 'makeID') { # Return ID from Alias
  1067. for my $clientID (keys %{$hash->{clients}}) {
  1068. $clientRef = $hash->{clients}->{$clientID};
  1069. if ( ($devAliases && $devAliases =~ /$clientID:$ID/)
  1070. || ($devAliases && defined $clientRef->{name} && $devAliases =~ /$clientRef->{name}:$ID/)
  1071. || ($devAliases && defined $clientRef->{hostname} && $devAliases =~ /$clientRef->{hostname}:$ID/)
  1072. || (defined $clientRef->{name} && $clientRef->{name} eq $ID)
  1073. || (defined $clientRef->{hostname} && $clientRef->{hostname} eq $ID)
  1074. ) {
  1075. $ID = $clientID;
  1076. last;
  1077. }
  1078. }
  1079. return $ID;
  1080. }
  1081. else { # Return all clients in a scalar
  1082. my $clients = '';
  1083. for my $clientID (keys %{$hash->{clients}}) {
  1084. $clients .= Unifi_ClientNames($hash,$clientID,'makeAlias').',';
  1085. }
  1086. $clients =~ s/.$//;
  1087. return $clients;
  1088. }
  1089. }
  1090. ###############################################################################
  1091. sub Unifi_ApNames($@) {
  1092. my ($hash,$ID,$W) = @_;
  1093. my $clientRef;
  1094. if(defined $ID && defined $W && $W eq 'makeName') { # Return Name or IP from ID
  1095. $clientRef = $hash->{accespoints}->{$ID};
  1096. if ( (defined $clientRef->{name} && $clientRef->{name} =~ /^([\w\.\-]+)$/)
  1097. || (defined $clientRef->{ip} && $clientRef->{ip} =~ /^([\w\.\-]+)$/)
  1098. ) {
  1099. $ID = $1;
  1100. }
  1101. return $ID;
  1102. }
  1103. elsif (defined $ID && defined $W && $W eq 'makeID') { # Return ID from Name or IP
  1104. for (keys %{$hash->{accespoints}}) {
  1105. $clientRef = $hash->{accespoints}->{$_};
  1106. if ( (defined $clientRef->{name} && $clientRef->{name} eq $ID)
  1107. || (defined $clientRef->{ip} && $clientRef->{ip} eq $ID)
  1108. ) {
  1109. $ID = $_;
  1110. last;
  1111. }
  1112. }
  1113. return $ID;
  1114. }
  1115. else { # Return all aps in a scalar
  1116. my $aps = '';
  1117. for my $apID (keys %{$hash->{accespoints}}) {
  1118. $aps .= Unifi_ApNames($hash,$apID,'makeName').',';
  1119. }
  1120. $aps =~ s/.$//;
  1121. return $aps;
  1122. }
  1123. }
  1124. ###############################################################################
  1125. sub Unifi_NextUpdateFn($$) {
  1126. my ($hash,$fn) = @_;
  1127. my $NextUpdateFn = 0;
  1128. for (keys %{$hash->{updateDispatch}}) { # {updateDispatch}->{callFn}[callFnRef,'receiveFn',receiveFnRef]
  1129. if($hash->{updateDispatch}->{$_}[1] && $hash->{updateDispatch}->{$_}[1] eq $fn) {
  1130. delete $hash->{updateDispatch}->{$_};
  1131. } elsif(!$NextUpdateFn && $hash->{updateDispatch}->{$_}[0] && $_ ne 'Unifi_ProcessUpdate') {
  1132. $NextUpdateFn = $hash->{updateDispatch}->{$_}[0];
  1133. }
  1134. }
  1135. if (!$NextUpdateFn && $hash->{updateDispatch}->{Unifi_ProcessUpdate}[0]) {
  1136. $NextUpdateFn = $hash->{updateDispatch}->{Unifi_ProcessUpdate}[0];
  1137. delete $hash->{updateDispatch}->{Unifi_ProcessUpdate};
  1138. }
  1139. $NextUpdateFn->($hash) if($NextUpdateFn);
  1140. return undef;
  1141. }
  1142. ###############################################################################
  1143. sub Unifi_ReceiveFailure($$) {
  1144. my ($hash,$meta) = @_;
  1145. my ($name,$self) = ($hash->{NAME},Unifi_Whowasi());
  1146. if (defined $meta->{msg}) {
  1147. if ($meta->{msg} eq 'api.err.LoginRequired') {
  1148. Log3 $name, 5, "$name ($self) - LoginRequired detected...";
  1149. if(Unifi_CONNECTED($hash)) {
  1150. Log3 $name, 5, "$name ($self) - I am the first who detected LoginRequired. Do re-login...";
  1151. Unifi_CONNECTED($hash,'disconnected');
  1152. Unifi_DoUpdate($hash);
  1153. return undef;
  1154. }
  1155. }
  1156. elsif ($meta->{msg} eq "api.err.NoSiteContext" || ($hash->{unifi}->{version} == 3 && $meta->{msg} eq "api.err.InvalidObject")) {
  1157. Log3 $name, 1, "$name ($self) - Failed! - state:'$meta->{rc}' - msg:'$meta->{msg}'"
  1158. ." - This error indicates that the <siteID> in your definition is wrong."
  1159. ." Try to modify your definition with <sideID> = default.";
  1160. }
  1161. else {
  1162. Log3 $name, 5, "$name ($self) - Failed! - state:'$meta->{rc}' - msg:'$meta->{msg}'";
  1163. }
  1164. } else {
  1165. Log3 $name, 5, "$name ($self) - Failed (without message)! - state:'$meta->{rc}'";
  1166. }
  1167. }
  1168. ###############################################################################
  1169. sub Unifi_CONNECTED($@) {
  1170. my ($hash,$set) = @_;
  1171. if ($set) {
  1172. $hash->{unifi}->{CONNECTED} = $set;
  1173. RemoveInternalTimer($hash);
  1174. %{$hash->{updateDispatch}} = ();
  1175. if (!defined($hash->{READINGS}->{state}->{VAL}) || $hash->{READINGS}->{state}->{VAL} ne $set) {
  1176. readingsSingleUpdate($hash,"state",$set,1);
  1177. }
  1178. return undef;
  1179. }
  1180. else {
  1181. if ($hash->{unifi}->{CONNECTED} eq 'disabled') {
  1182. return 'disabled';
  1183. }
  1184. elsif ($hash->{unifi}->{CONNECTED} eq 'connected') {
  1185. return 1;
  1186. } else {
  1187. return 0;
  1188. }
  1189. }
  1190. }
  1191. ###############################################################################
  1192. sub Unifi_Whoami() { return (split('::',(caller(1))[3]))[1] || ''; }
  1193. sub Unifi_Whowasi() { return (split('::',(caller(2))[3]))[1] || ''; }
  1194. ###############################################################################
  1195. 1;
  1196. =pod
  1197. =item device
  1198. =item summary Interpret / control of Ubiquiti Networks UniFi-controller
  1199. =item summary_DE Auswertung / Steuerung eines Ubiquiti Networks UniFi-Controller
  1200. =begin html
  1201. <a name="Unifi"></a>
  1202. <h3>Unifi</h3>
  1203. <ul>
  1204. Unifi is the FHEM module for the Ubiquiti Networks (UBNT) - Unifi Controller.<br>
  1205. <br>
  1206. e.g. you can use the 'presence' function, which will tell you if a device is connected to your WLAN (even in PowerSave Mode!).<br>
  1207. Immediately after connecting to your WLAN it will set the device-reading to 'connected' and about 5 minutes after leaving your WLAN it will set the reading to 'disconnected'.<br>
  1208. The device will be still connected, even it is in PowerSave-Mode. (In this mode the devices are not pingable, but the connection to the unifi-controller does not break off.)<br>
  1209. <br>
  1210. Or you can use the other readings or set and get features to control your unifi-controller, accesspoints and wlan-clients.
  1211. <br>
  1212. <h4>Prerequisites</h4>
  1213. <ul>
  1214. The Perl module JSON is required. <br>
  1215. On Debian/Raspbian: <code>apt-get install libjson-perl </code><br>
  1216. Via CPAN: <code>cpan install JSON</code>
  1217. </ul>
  1218. <h4>Define</h4>
  1219. <ul>
  1220. <code>define &lt;name&gt; Unifi &lt;ip&gt; &lt;port&gt; &lt;username&gt; &lt;password&gt; [&lt;interval&gt; [&lt;siteID&gt; [&lt;version&gt;]]]</code>
  1221. <br><br>
  1222. <br>
  1223. &lt;name&gt;:
  1224. <ul>
  1225. <code>The FHEM device name for the device.</code><br>
  1226. </ul>
  1227. &lt;ip&gt;:
  1228. <ul>
  1229. <code>The ip of your unifi-controller.</code><br>
  1230. </ul>
  1231. &lt;port&gt;:
  1232. <ul>
  1233. <code>The port of your unifi-controller. Normally it's 8443 or 443.</code><br>
  1234. </ul>
  1235. &lt;username&gt;:
  1236. <ul>
  1237. <code>The Username to log on.</code><br>
  1238. </ul>
  1239. &lt;password&gt;:
  1240. <ul>
  1241. <code>The password to log on.</code><br>
  1242. </ul>
  1243. [&lt;interval&gt;]:
  1244. <ul>
  1245. <code>(optional without &lt;siteID&gt; and &lt;version&gt;)<br>
  1246. Interval to fetch the information from the unifi-api. <br>
  1247. default: 30 seconds</code><br>
  1248. </ul>
  1249. [&lt;siteID&gt;]:
  1250. <ul>
  1251. <code>(optional without &lt;version&gt;)<br>
  1252. You can find the site-ID by selecting the site in the UniFi web interface.<br>
  1253. e.g. https://192.168.12.13:8443/manage/s/foobar the siteId you must use is: foobar.<br>
  1254. default: default</code><br>
  1255. </ul>
  1256. [&lt;version&gt;]:
  1257. <ul>
  1258. <code>(optional if you use unifi v4)<br>
  1259. Unifi-controller version. <br>
  1260. Version must be specified if version is not 4. At the moment version 3 and 4 are supported.<br>
  1261. default: 4</code><br>
  1262. </ul> <br>
  1263. </ul>
  1264. <h4>Examples</h4>
  1265. <ul>
  1266. <code>define my_unifi_controller Unifi 192.168.1.15 443 admin secret</code><br>
  1267. <br>
  1268. Or with optional parameters &lt;interval&gt;, &lt;siteID&gt; and &lt;version&gt;:<br>
  1269. <code>define my_unifi_controller Unifi 192.168.1.15 443 admin secret 30 default 3</code><br>
  1270. </ul>
  1271. <h4>Set</h4>
  1272. <ul>
  1273. <code>Note: Some setters are not available if controller is not connected, or no data is available for them.</code><br>
  1274. <br>
  1275. <li><code>set &lt;name&gt; update</code><br>
  1276. Makes immediately a manual update. </li>
  1277. <br>
  1278. <li><code>set &lt;name&gt; clear &lt;readings|clientData|all&gt;</code><br>
  1279. Clears the readings, clientData or all. </li>
  1280. <br>
  1281. <li><code>set &lt;name&gt; archiveAlerts</code><br>
  1282. Archive all unarchived Alerts. </li>
  1283. <br>
  1284. <li><code>set &lt;name&gt; disconnectClient &lt;all|user_id|controllerAlias|hostname|devAlias&gt;</code><br>
  1285. Disconnect one ore all clients. </li>
  1286. <br>
  1287. <li><code>set &lt;name&gt; restartAP &lt;all|_id|name|ip&gt;</code><br>
  1288. Restart one ore all accesspoints. </li>
  1289. <br>
  1290. <li><code>set &lt;name&gt; setLocateAP &lt;all|_id|name|ip&gt;</code><br>
  1291. Start 'locate' on one or all accesspoints. </li>
  1292. <br>
  1293. <li><code>set &lt;name&gt; unsetLocateAP &lt;all|_id|name|ip&gt;</code><br>
  1294. Stop 'locate' on one or all accesspoints. </li>
  1295. <li><code>set &lt;name&gt; poeMode &lt;name|mac|id&gt; &lt;port&gt; &lt;off|auto|passive|passthrough|restart&gt;</code><br>
  1296. Set PoE mode for &lt;port&gt;. </li>
  1297. </ul>
  1298. <h4>Get</h4>
  1299. <ul>
  1300. <code>Note: Some getters are not available if no data is available for them.</code><br>
  1301. <br>
  1302. <li><code>get &lt;name&gt; clientData &lt;all|user_id|controllerAlias|hostname|devAlias&gt;</code><br>
  1303. Show more details about clients.</li>
  1304. <br>
  1305. <li><code>get &lt;name&gt; events</code><br>
  1306. Show events in specified 'eventPeriod'.</li>
  1307. <br>
  1308. <li><code>get &lt;name&gt; unarchivedAlerts</code><br>
  1309. Show all unarchived Alerts.</li>
  1310. <li><code>get &lt;name&gt; poeState [name|mac|id]</code><br>
  1311. Show port PoE state.</li>
  1312. </ul>
  1313. <h4>Attributes</h4>
  1314. <ul>
  1315. <li>attr devAlias<br>
  1316. Can be used to rename device names in the format <code>&lt;user_id|controllerAlias|hostname&gt;:Aliasname.</code><br>
  1317. Separate using blank to rename multiple devices.<br>
  1318. Example (user_id):<code> attr unifi devAlias 5537d138e4b033c1832c5c84:iPhone-Claudiu</code><br>
  1319. Example (controllerAlias):<code> attr unifi devAlias iPhoneControllerAlias:iPhone-Claudiu</code><br>
  1320. Example (hostname):<code> attr unifi devAlias iphone:iPhone-Claudiu</code><br></li>
  1321. <br>
  1322. <li>attr eventPeriod &lt;1...168&gt;<br>
  1323. Can be used to configure the time-period (hours) of fetched events from controller.<br>
  1324. <code>default: 24</code></li>
  1325. <br>
  1326. <li>attr disable &lt;1|0&gt;<br>
  1327. With this attribute you can disable the whole module. <br>
  1328. If set to 1 the module will be stopped and no updates are performed.<br>
  1329. If set to 0 the automatic updating will performed.</li>
  1330. <br>
  1331. <li>attr <a href="#verbose">verbose</a> 5<br>
  1332. This attribute will help you if something does not work as espected.</li>
  1333. <br>
  1334. <li>attr httpLoglevel <1,2,3,4,5><br>
  1335. Can be used to debug the HttpUtils-Module. Set it smaller or equal as your 'global verbose level'.<br>
  1336. <code>default: 5</code></li>
  1337. <br>
  1338. <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  1339. </ul>
  1340. <h4>Readings</h4>
  1341. <ul>
  1342. Note: All readings generate events. You can control this with <a href="#readingFnAttributes">these global attributes</a>.
  1343. <li>Each client has 6 readings for connection-state, SNR, uptime, last_seen-time, connected-AP and hostname.</li>
  1344. <li>Each AP has 3 readings for state (can be 'ok' or 'error'), essid's and count of connected-clients.</li>
  1345. <li>The unifi-controller has 6 readings for event-count in configured 'timePeriod', unarchived-alert count, accesspoint count, overall wlan-state (can be 'ok', 'warning', or other?), connected user count and connected guest count. </li>
  1346. <li>The Unifi-device reading 'state' represents the connection-state to the unifi-controller (can be 'connected', 'disconnected', 'initialized' and 'disabled').</li>
  1347. </ul>
  1348. <br>
  1349. </ul>
  1350. =end html
  1351. =cut