74_Unifi.pm 55 KB

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