74_Unifi.pm 100 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333
  1. ##############################################################################
  2. # $Id: 74_Unifi.pm 16383 2018-03-11 14:54:39Z wuehler $
  3. # CHANGED
  4. ##############################################################################
  5. # V 2.0
  6. # - feature: 74_Unifi: add new set commands to block/unblock clients,
  7. # enable/disable WLAN, new client-Reading essid
  8. # V 2.1
  9. # - feature: 74_Unifi: add new set command to en-/disable Site Status-LEDs
  10. # V 2.1.1
  11. # - bugfix: 74_Unifi: fixed blockClient
  12. # V 2.1.2
  13. # - feature: 74_Unifi: new Readings for WLAN-states, fixed Warning
  14. # V 2.1.3
  15. # - change: 74_Unifi: SSIDs-Readings and drop-downs use goodReadingName()
  16. # V 2.1.4
  17. # - feature: 74_Unifi: added voucher-functions
  18. # V 2.2
  19. # - feature: 74_Unifi: added set updateClien, encrypt user and password
  20. package main;
  21. use strict;
  22. use warnings;
  23. use HttpUtils;
  24. use POSIX;
  25. use JSON qw(decode_json);
  26. ##############################################################################}
  27. ### Forward declarations ####################################################{
  28. sub Unifi_Initialize($$);
  29. sub Unifi_Define($$);
  30. sub Unifi_Undef($$);
  31. sub Unifi_Notify($$);
  32. sub Unifi_Set($@);
  33. sub Unifi_Get($@);
  34. sub Unifi_Attr(@);
  35. sub Unifi_DoUpdate($@);
  36. sub Unifi_Login_Send($);
  37. sub Unifi_Login_Receive($);
  38. sub Unifi_GetClients_Send($);
  39. sub Unifi_GetClients_Receive($);
  40. sub Unifi_GetWlans_Send($);
  41. sub Unifi_GetWlans_Receive($);
  42. sub Unifi_GetHealth_Send($);
  43. sub Unifi_GetHealth_Receive($);
  44. sub Unifi_GetWlanGroups_Send($);
  45. sub Unifi_GetWlanGroups_Receive($);
  46. sub Unifi_GetUnarchivedAlerts_Send($);
  47. sub Unifi_GetUnarchivedAlerts_Receive($);
  48. sub Unifi_GetEvents_Send($);
  49. sub Unifi_GetEvents_Receive($);
  50. sub Unifi_GetAccesspoints_Send($);
  51. sub Unifi_GetAccesspoints_Receive($);
  52. sub Unifi_ProcessUpdate($);
  53. sub Unifi_SetClientReadings($);
  54. sub Unifi_SetHealthReadings($);
  55. sub Unifi_SetAccesspointReadings($);
  56. sub Unifi_SetWlanReadings($);
  57. sub Unifi_DisconnectClient_Send($@);
  58. sub Unifi_DisconnectClient_Receive($);
  59. sub Unifi_ApCmd_Send($$@);
  60. sub Unifi_ApCmd_Receive($);
  61. sub Unifi_ArchiveAlerts_Send($);
  62. sub Unifi_Cmd_Receive($);
  63. sub Unifi_ClientNames($@);
  64. sub Unifi_ApNames($@);
  65. sub Unifi_SSIDs($@);
  66. sub Unifi_BlockClient_Send($@);
  67. sub Unifi_BlockClient_Receive($);
  68. sub Unifi_UnblockClient_Send($@);
  69. sub Unifi_UnblockClient_Receive($);
  70. sub Unifi_UpdateClient_Send($$);
  71. sub Unifi_UpdateClient_Receive($);
  72. sub Unifi_SwitchSiteLEDs_Send($$);
  73. sub Unifi_SwitchSiteLEDs_Receive($);
  74. sub Unifi_WlanconfRest_Send($$@);
  75. sub Unifi_WlanconfRest_Receive($);
  76. sub Unifi_GetVoucherList_Send($);
  77. sub Unifi_GetVoucherList_Receive($);
  78. sub Unifi_CreateVoucher_Send($%);
  79. sub Unifi_CreateVoucher_Receive($);
  80. sub Unifi_SetVoucherReadings($);
  81. sub Unifi_initVoucherCache($);
  82. sub Unifi_getNextVoucherForNote($$);
  83. sub Unifi_NextUpdateFn($$);
  84. sub Unifi_ReceiveFailure($$);
  85. sub Unifi_CONNECTED($@);
  86. sub Unifi_encrypt($);
  87. sub Unifi_encrypt($);
  88. sub Unifi_Whoami();
  89. sub Unifi_Whowasi();
  90. ##############################################################################}
  91. sub Unifi_Initialize($$) {
  92. my ($hash) = @_;
  93. $hash->{DefFn} = "Unifi_Define";
  94. $hash->{UndefFn} = "Unifi_Undef";
  95. $hash->{SetFn} = "Unifi_Set";
  96. $hash->{GetFn} = "Unifi_Get";
  97. $hash->{AttrFn} = 'Unifi_Attr';
  98. $hash->{NotifyFn} = "Unifi_Notify";
  99. $hash->{AttrList} = "disable:1,0 "
  100. ."devAlias "
  101. ."ignoreWiredClients:1,0 "
  102. ."ignoreWirelessClients:1,0 "
  103. ."httpLoglevel:1,2,3,4,5 "
  104. ."eventPeriod "
  105. ."deprecatedClientNames:1,0 "
  106. ."voucherCache "
  107. .$readingFnAttributes;
  108. }
  109. ###############################################################################
  110. sub Unifi_Define($$) {
  111. my ($hash, $def) = @_;
  112. my @a = split("[ \t][ \t]*", $def);
  113. return "Wrong syntax: use define <name> Unifi <ip> <port> <username> <password> [<interval> [<siteID> [<version>]]]" if(int(@a) < 6);
  114. return "Wrong syntax: <port> is not a number!" if(!looks_like_number($a[3]));
  115. return "Wrong syntax: <interval> is not a number!" if($a[6] && !looks_like_number($a[6]));
  116. return "Wrong syntax: <interval> too small, must be at least 5" if($a[6] && $a[6] < 5);
  117. 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/));
  118. #TODO: Passwort verschlüsseln! (ala Harmony?)
  119. my $name = $a[0];
  120. %$hash = ( %$hash,
  121. NOTIFYDEV => 'global',
  122. unifi => {
  123. CONNECTED => 0,
  124. eventPeriod => int(AttrVal($name,"eventPeriod",24)),
  125. deprecatedClientNames => int(AttrVal($name,"deprecatedClientNames",1)),
  126. interval => $a[6] || 30,
  127. version => $a[8] || 4,
  128. url => "https://".$a[2].(($a[3] == 443) ? '' : ':'.$a[3]).'/api/s/'.(($a[7]) ? $a[7] : 'default').'/',
  129. },
  130. );
  131. $hash->{httpParams} = {
  132. hash => $hash,
  133. timeout => 5,
  134. method => "POST",
  135. noshutdown => 0,
  136. ignoreredirects => 1,
  137. loglevel => AttrVal($name,"httpLoglevel",5),
  138. sslargs => { SSL_verify_mode => 0 },
  139. };
  140. #if($hash->{unifi}->{version} == 3) {
  141. # ( $hash->{httpParams}->{loginUrl} = $hash->{unifi}->{url} ) =~ s/api\/s.+/login/;
  142. # $hash->{httpParams}->{loginData} = "login=login&username=".$a[4]."&password=".$a[5];
  143. #}else {
  144. # ( $hash->{httpParams}->{loginUrl} = $hash->{unifi}->{url} ) =~ s/api\/s.+/api\/login/;
  145. # $hash->{httpParams}->{loginData} = '{"username":"'.$a[4].'", "password":"'.$a[5].'"}';
  146. #}
  147. my $username = Unifi_encrypt($a[4]);
  148. my $password = Unifi_encrypt($a[5]);
  149. $hash->{helper}{username} = $username;
  150. $hash->{helper}{password} = $password;
  151. my $define="$a[2] $a[3] $username $password";
  152. $define.=" $a[6]" if($a[6]);
  153. $define.=" $a[7]" if($a[7]);
  154. $define.=" $a[8]" if($a[8]);
  155. $hash->{DEF} = $define;
  156. Log3 $name, 5, "$name: Defined with url:$hash->{unifi}->{url}, interval:$hash->{unifi}->{interval}, version:$hash->{unifi}->{version}";
  157. return undef;
  158. }
  159. ###############################################################################
  160. sub Unifi_Undef($$) {
  161. my ($hash,$arg) = @_;
  162. RemoveInternalTimer($hash);
  163. return undef;
  164. }
  165. ###############################################################################
  166. sub Unifi_Notify($$) {
  167. my ($hash,$dev) = @_;
  168. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  169. return if($dev->{NAME} ne "global");
  170. return if(!grep(m/^DEFINED|MODIFIED|INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}}));
  171. if(AttrVal($name, "disable", 0)) {
  172. Log3 $name, 5, "$name ($self) - executed. - Device '$name' is disabled, do nothing...";
  173. Unifi_CONNECTED($hash,'disabled');
  174. } else {
  175. Log3 $name, 5, "$name ($self) - executed. - Remove all Timers & Call DoUpdate...";
  176. Unifi_CONNECTED($hash,'initialized');
  177. Unifi_DoUpdate($hash);
  178. }
  179. return undef;
  180. }
  181. ###############################################################################
  182. sub Unifi_Set($@) {
  183. my ($hash,@a) = @_;
  184. return "\"set $hash->{NAME}\" needs at least an argument" if ( @a < 2 );
  185. # setVal4 enthält nur erstes Wort der note für voucher!!!
  186. # in Doku aufgenommen, dass genau drei Leerzeichen enthalten sein müssen, also note keine Leerzeichen enthalten kann
  187. my ($name,$setName,$setVal,$setVal2,$setVal3,$setVal4) = @a;
  188. Log3 $name, 5, "$name: set called with $setName " . ($setVal ? $setVal : "") if ($setName ne "?");
  189. if(Unifi_CONNECTED($hash) eq 'disabled' && $setName !~ /clear/) {
  190. return "Unknown argument $setName, choose one of clear:all,readings,clientData,voucherCache";
  191. Log3 $name, 5, "$name: set called with $setName but device is disabled!" if($setName ne "?");
  192. return undef;
  193. }
  194. my $clientNames = Unifi_ClientNames($hash);
  195. my $apNames = Unifi_ApNames($hash);
  196. my $SSIDs = Unifi_SSIDs($hash);
  197. if($setName !~ /archiveAlerts|restartAP|setLocateAP|unsetLocateAP|disconnectClient|update|updateClient|clear|poeMode|blockClient|unblockClient|enableWLAN|disableWLAN|switchSiteLEDs|createVoucher/) {
  198. return "Unknown argument $setName, choose one of update:noArg "
  199. ."clear:all,readings,clientData,allData,voucherCache "
  200. .((defined $hash->{alerts_unarchived}[0] && scalar @{$hash->{alerts_unarchived}}) ? "archiveAlerts:noArg " : "")
  201. .(($apNames && Unifi_CONNECTED($hash)) ? "restartAP:all,$apNames setLocateAP:all,$apNames unsetLocateAP:all,$apNames " : "")
  202. .(($clientNames && Unifi_CONNECTED($hash)) ? "disconnectClient:all,$clientNames " : "")
  203. ."poeMode createVoucher enableWLAN:$SSIDs disableWLAN:$SSIDs "
  204. ."blockClient:$clientNames unblockClient:$clientNames switchSiteLEDs:on,off updateClient";
  205. }
  206. else {
  207. Log3 $name, 4, "$name: set $setName";
  208. if (defined $hash->{unifi}->{deprecatedClientNames} && $hash->{unifi}->{deprecatedClientNames} eq 1){
  209. Log3 $name, 2, "$name: deprecated use of Attribute 'deprecatedClientNames' (see commandref for details).";
  210. }
  211. if (Unifi_CONNECTED($hash)) {
  212. if ($setName eq 'disconnectClient') {
  213. if ($setVal && $setVal ne 'all') {
  214. $setVal = Unifi_ClientNames($hash,$setVal,'makeID');
  215. if (defined $hash->{clients}->{$setVal}) {
  216. Unifi_DisconnectClient_Send($hash,$setVal);
  217. }
  218. else {
  219. return "$hash->{NAME}: Unknown client '$setVal' in command '$setName', choose one of: all,$clientNames";
  220. }
  221. }
  222. elsif (!$setVal || $setVal eq 'all') {
  223. Unifi_DisconnectClient_Send($hash,keys(%{$hash->{clients}}));
  224. }
  225. }
  226. elsif ($setName eq 'blockClient') {
  227. if ($setVal && $setVal ne 'all') {
  228. $setVal = Unifi_ClientNames($hash,$setVal,'makeID');
  229. if (defined $hash->{clients}->{$setVal}) {
  230. Unifi_BlockClient_Send($hash,$setVal);
  231. }
  232. else {
  233. return "$hash->{NAME}: Unknown client '$setVal' in command '$setName', choose one of: all,$clientNames";
  234. }
  235. }
  236. elsif (!$setVal || $setVal eq 'all') {
  237. Unifi_BlockClient_Send($hash,keys(%{$hash->{clients}}));
  238. }
  239. }
  240. elsif ($setName eq 'unblockClient') {
  241. if ($setVal && $setVal ne 'all') {
  242. $setVal = Unifi_ClientNames($hash,$setVal,'makeID');
  243. if (defined $hash->{clients}->{$setVal}) {
  244. Unifi_UnblockClient_Send($hash,$setVal);
  245. }
  246. else {
  247. return "$hash->{NAME}: Unknown client '$setVal' in command '$setName', choose one of: all,$clientNames";
  248. }
  249. }
  250. elsif (!$setVal || $setVal eq 'all') {
  251. Unifi_UnblockClient_Send($hash,keys(%{$hash->{clients}}));
  252. }
  253. }
  254. elsif ($setName eq 'switchSiteLEDs') {
  255. my $state="true";
  256. if ($setVal && $setVal eq 'off') {
  257. $state="false";
  258. }
  259. Unifi_SwitchSiteLEDs_Send($hash,$state);
  260. }
  261. elsif ($setName eq 'disableWLAN') {
  262. my $wlanid = Unifi_SSIDs($hash,$setVal,'makeID');
  263. if (defined $hash->{wlans}->{$wlanid}) {
  264. my $wlanconf = $hash->{wlans}->{$wlanid};
  265. $wlanconf->{enabled}=JSON::false;
  266. Unifi_WlanconfRest_Send($hash,$wlanid,$wlanconf);
  267. }
  268. else {
  269. return "$hash->{NAME}: Unknown SSID '$setVal' in command '$setName', choose one of: all,$SSIDs";
  270. }
  271. }
  272. elsif ($setName eq 'enableWLAN') {
  273. my $wlanid = Unifi_SSIDs($hash,$setVal,'makeID');
  274. if (defined $hash->{wlans}->{$wlanid}) {
  275. my $wlanconf = $hash->{wlans}->{$wlanid};
  276. $wlanconf->{enabled}=JSON::true;
  277. Unifi_WlanconfRest_Send($hash,$wlanid,$wlanconf);
  278. }
  279. else {
  280. return "$hash->{NAME}: Unknown SSID '$setVal' in command '$setName', choose one of: all,$SSIDs";
  281. }
  282. }
  283. elsif ($setName eq 'updateClient') {
  284. return "enter mac of client" if( ! defined $setVal);
  285. Unifi_UpdateClient_Send($hash,$setVal);
  286. }
  287. elsif ($setName eq 'poeMode') {
  288. return "usage: $setName <name|mac|id> <port> <off|auto|passive|passthrough|restart>" if( !$setVal3 );
  289. my $apRef;
  290. for my $apID (keys %{$hash->{accespoints}}) {
  291. my $ap = $hash->{accespoints}->{$apID};
  292. next if( !$ap->{port_table} );
  293. next if( $ap->{type} ne 'usw' );
  294. next if( $setVal ne $ap->{mac} && $setVal ne $ap->{device_id} && $ap->{name} !~ $setVal );
  295. return "multiple switches found for $setVal" if( $apRef );
  296. $apRef = $ap;
  297. }
  298. return "no switch $setVal found" if( !$apRef );
  299. if( $setVal2 !~ m/\d+/ ) {
  300. for my $port (@{$apRef->{port_table}}) {
  301. next if( $port->{name} !~ $setVal2 );
  302. $setVal2 = $port->{port_idx};
  303. last;
  304. }
  305. }
  306. return "port musst be numeric" if( $setVal2 !~ m/\d+/ );
  307. return "port musst be in [1..". scalar @{$apRef->{port_table}} ."] " if( $setVal2 < 1 || $setVal2 > scalar @{$apRef->{port_table}} );
  308. return "switch '$apRef->{name}' has no port $setVal2" if( !defined(@{$apRef->{port_table}}[$setVal2-1] ) );
  309. return "port $setVal2 of switch '$apRef->{name}' is not poe capable" if( !@{$apRef->{port_table}}[$setVal2-1]->{port_poe} );
  310. my $port_overrides = $apRef->{port_overrides};
  311. my $idx;
  312. my $i = 0;
  313. for my $entry (@{$port_overrides}) {
  314. if( $entry->{port_idx} eq $setVal2 ) {
  315. $idx = $i;
  316. last;
  317. }
  318. ++$i;
  319. }
  320. if( !defined($idx) ) {
  321. push @{$port_overrides}, {port_idx => $setVal2+0};
  322. $idx = scalar @{$port_overrides};
  323. }
  324. if( $setVal3 eq 'off' ) {
  325. $port_overrides->[$idx]{poe_mode} = "off";
  326. Unifi_RestJson_Send($hash, $apRef->{device_id}, {port_overrides => $port_overrides });
  327. } elsif( $setVal3 eq 'auto' || $setVal3 eq 'poe+' ) {
  328. #return "port $setVal2 not auto poe capable" if( @{$apRef->{port_table}}[$setVal2-1]->{poe_caps} & 0x03 ) ;
  329. $port_overrides->[$idx]{poe_mode} = "auto";
  330. Unifi_RestJson_Send($hash, $apRef->{device_id}, {port_overrides => $port_overrides });
  331. } elsif( $setVal3 eq 'passive' ) {
  332. #return "port $setVal2 not passive poe capable" if( @{$apRef->{port_table}}[$setVal2-1]->{poe_caps} & 0x04 ) ;
  333. $port_overrides->[$idx]{poe_mode} = "pasv24";
  334. Unifi_RestJson_Send($hash, $apRef->{device_id}, {port_overrides => $port_overrides });
  335. } elsif( $setVal3 eq 'passthrough' ) {
  336. #return "port $setVal2 not passthrough poe capable" if( @{$apRef->{port_table}}[$setVal2-1]->{poe_caps} & 0x08 ) ;
  337. $port_overrides->[$idx]{poe_mode} = "passthrough";
  338. Unifi_RestJson_Send($hash, $apRef->{device_id}, {port_overrides => $port_overrides });
  339. } elsif( $setVal3 eq 'restsart' ) {
  340. Unifi_ApJson_Send($hash,{cmd => 'power-cycle', mac => $apRef->{mac}, port_idx => $setVal2+0});
  341. } else {
  342. return "unknwon poe mode $setVal3";
  343. }
  344. }
  345. elsif ($setName eq 'archiveAlerts' && defined $hash->{alerts_unarchived}[0]) {
  346. Unifi_ArchiveAlerts_Send($hash);
  347. undef @{$hash->{alerts_unarchived}};
  348. }
  349. elsif ($setName eq 'restartAP') {
  350. if ($setVal && $setVal ne 'all') {
  351. $setVal = Unifi_ApNames($hash,$setVal,'makeID');
  352. if (defined $hash->{accespoints}->{$setVal}) {
  353. Unifi_ApCmd_Send($hash,'restart',$setVal);
  354. }
  355. else {
  356. return "$hash->{NAME}: Unknown accesspoint '$setVal' in command '$setName', choose one of: all,$apNames";
  357. }
  358. }
  359. elsif (!$setVal || $setVal eq 'all') {
  360. Unifi_ApCmd_Send($hash,'restart',keys(%{$hash->{accespoints}}));
  361. }
  362. }
  363. elsif ($setName eq 'setLocateAP') {
  364. if ($setVal && $setVal ne 'all') {
  365. $setVal = Unifi_ApNames($hash,$setVal,'makeID');
  366. if (defined $hash->{accespoints}->{$setVal}) {
  367. Unifi_ApCmd_Send($hash,'set-locate',$setVal);
  368. }
  369. else {
  370. return "$hash->{NAME}: Unknown accesspoint '$setVal' in command '$setName', choose one of: all,$apNames";
  371. }
  372. }
  373. elsif (!$setVal || $setVal eq 'all') {
  374. Unifi_ApCmd_Send($hash,'set-locate',keys(%{$hash->{accespoints}}));
  375. }
  376. }
  377. elsif ($setName eq 'unsetLocateAP') {
  378. if ($setVal && $setVal ne 'all') {
  379. $setVal = Unifi_ApNames($hash,$setVal,'makeID');
  380. if (defined $hash->{accespoints}->{$setVal}) {
  381. Unifi_ApCmd_Send($hash,'unset-locate',$setVal);
  382. }
  383. else {
  384. return "$hash->{NAME}: Unknown accesspoint '$setVal' in command '$setName', choose one of: all,$apNames";
  385. }
  386. }
  387. elsif (!$setVal || $setVal eq 'all') {
  388. Unifi_ApCmd_Send($hash,'unset-locate',keys(%{$hash->{accespoints}}));
  389. }
  390. }
  391. elsif ($setName eq 'createVoucher') {
  392. if (!looks_like_number($setVal) || int($setVal) < 1 ||
  393. !looks_like_number($setVal2) || int($setVal2) < 1 ||
  394. !looks_like_number($setVal3) || int($setVal3) < 1 ||
  395. $setVal4 eq "") {
  396. return "$hash->{NAME} $setName: First three arguments (expire, n, quota) must be numeric. Forth argument is note of voucher."
  397. }
  398. if ($setVal4 =~ /,/) {
  399. return "$hash->{NAME} $setName: Note of voucher has invalid character (,)."
  400. }
  401. my %params=("expire"=>$setVal,"n"=>$setVal2,"quota"=>$setVal3,"note"=>$setVal4);
  402. Unifi_CreateVoucher_Send($hash, %params);
  403. }
  404. }
  405. if ($setName eq 'update') {
  406. RemoveInternalTimer($hash);
  407. Unifi_DoUpdate($hash,1);
  408. }
  409. elsif ($setName eq 'clear') {
  410. if ($setVal eq 'readings' || $setVal eq 'all') {
  411. for (keys %{$hash->{READINGS}}) {
  412. delete $hash->{READINGS}->{$_} if($_ ne 'state');
  413. }
  414. }
  415. if ($setVal eq 'clientData') {
  416. %{$hash->{clients}} = ();
  417. }
  418. if ($setVal eq 'allData' || $setVal eq 'all') {
  419. %{$hash->{clients}} = ();
  420. %{$hash->{wlans}} = ();
  421. %{$hash->{wlan_health}} = ();
  422. %{$hash->{accespoints}} = ();
  423. # %{$hash->{events}} = ();
  424. %{$hash->{wlangroups}} = ();
  425. # %{$hash->{alerts_unarchived}} = ();
  426. }
  427. if ($setVal eq 'voucherCache' || $setVal eq 'all') {
  428. my $cache_attr_value=$hash->{hotspot}->{voucherCache}->{attr_value};
  429. %{$hash->{hotspot}->{voucherCache}} = ();
  430. $hash->{hotspot}->{voucherCache}->{attr_value} = $cache_attr_value;
  431. Unifi_initVoucherCache($hash);
  432. }
  433. }
  434. }
  435. return undef;
  436. }
  437. ###############################################################################
  438. sub Unifi_Get($@) {
  439. my ($hash,@a) = @_;
  440. return "\"get $hash->{NAME}\" needs at least one argument" if ( @a < 2 );
  441. my ($name,$getName,$getVal) = @a;
  442. if (defined $getVal){
  443. Log3 $name, 5, "$name: get called with $getName $getVal." ;
  444. }else{
  445. Log3 $name, 5, "$name: get called with $getName.";
  446. }
  447. my %voucherNotesHash= ();
  448. my $voucherNote = '';
  449. if(defined $hash->{hotspot}->{vouchers}[0]){
  450. for my $voucher (@{$hash->{hotspot}->{vouchers}}) {
  451. if(defined $voucher->{note} && $voucher->{note} =~ /^((?!,).)*$/ && $voucher->{note} ne ""){
  452. $voucherNote = $voucher->{note};
  453. $voucherNote =~ s/( )/&nbsp;/og;
  454. $voucherNotesHash{$voucherNote}=$voucherNote;
  455. }else{
  456. Log3 $name, 4, "$name Info: vouchers without note or containing comma(,) in note or with empty note are ignored in drop-downs.";
  457. }
  458. }
  459. }
  460. my $voucherNotes=join(",", keys %voucherNotesHash);
  461. my $clientNames = Unifi_ClientNames($hash);
  462. if($getName !~ /events|clientData|unarchivedAlerts|poeState|voucherList|voucher|showAccount/) {
  463. return "Unknown argument $getName, choose one of "
  464. .((defined $hash->{events}[0] && scalar @{$hash->{events}}) ? "events:noArg " : "")
  465. .((defined $hash->{alerts_unarchived}[0] && scalar @{$hash->{alerts_unarchived}}) ? "unarchivedAlerts:noArg " : "")
  466. .(($clientNames) ? "clientData:all,$clientNames " : "")
  467. ."poeState voucherList:all,$voucherNotes voucher:$voucherNotes showAccount";
  468. }
  469. elsif ($getName eq 'poeState') {
  470. my $poeState;
  471. for my $apID (keys %{$hash->{accespoints}}) {
  472. my $apRef = $hash->{accespoints}->{$apID};
  473. next if( $apRef->{type} ne 'usw' );
  474. next if( !$apRef->{port_table} );
  475. next if( $getVal && $getVal ne $apRef->{mac} && $getVal ne $apRef->{device_id} && $apRef->{name} !~ $getVal );
  476. $poeState .= "\n" if( $poeState );
  477. $poeState .= sprintf( "%-20s (mac:%-17s, id:%s)\n", $apRef->{name}, $apRef->{mac}, $apRef->{device_id} );
  478. $poeState .= sprintf( " %2s %-15s", "id", "name" );
  479. $poeState .= sprintf( " %s %s %-6s %-4s %-10s", "", "on", "mode", "", "class" );
  480. $poeState .= "\n";
  481. for my $port (@{$apRef->{port_table}}) {
  482. #next if( !$port->{port_poe} );
  483. $poeState .= sprintf( " %2i %-15s", $port->{port_idx}, $port->{name} );
  484. $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} );
  485. $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} );
  486. $poeState .= "\n";
  487. }
  488. }
  489. $poeState = "====================================================\n". $poeState;
  490. $poeState .= "====================================================\n";
  491. return $poeState;
  492. }
  493. elsif ($getName eq 'unarchivedAlerts' && defined $hash->{alerts_unarchived}[0] && scalar @{$hash->{alerts_unarchived}}) {
  494. my $alerts = "====================================================\n";
  495. for my $alert (@{$hash->{alerts_unarchived}}) {
  496. for (sort keys %{$alert}) {
  497. if ($_ !~ /^(archived|_id|handled_admin_id|site_id|datetime|handled_time)$/) {
  498. $alert->{$_} = strftime "%Y-%m-%d %H:%M:%S",localtime($alert->{$_} / 1000) if($_ eq 'time');
  499. $alerts .= "$_ = ".((defined $alert->{$_}) ? $alert->{$_} : '')."\n";
  500. }
  501. }
  502. $alerts .= "====================================================\n";
  503. }
  504. return $alerts;
  505. }
  506. elsif ($getName eq 'events' && defined $hash->{events}[0] && scalar @{$hash->{events}}) {
  507. my $events = "==================================================================\n";
  508. for my $event (@{$hash->{events}}) {
  509. for (sort keys %{$event}) {
  510. if ($_ !~ /^(_id|site_id|subsystem|datetime|is_admin)$/) {
  511. $event->{$_} = strftime "%Y-%m-%d %H:%M:%S",localtime($event->{$_} / 1000) if($_ eq 'time');
  512. $events .= "$_ = ".((defined $event->{$_}) ? $event->{$_} : '')."\n";
  513. }
  514. }
  515. $events .= "==================================================================\n";
  516. }
  517. return $events;
  518. }
  519. elsif ($getName eq 'clientData' && $clientNames) {
  520. my $clientData = '';
  521. if ($getVal && $getVal ne 'all') {
  522. $getVal = Unifi_ClientNames($hash,$getVal,'makeID');
  523. }
  524. if (!$getVal || $getVal eq 'all') {
  525. $clientData .= "======================================\n";
  526. for my $client (sort keys %{$hash->{clients}}) {
  527. for (sort keys %{$hash->{clients}->{$client}}) {
  528. $clientData .= "$_ = ".((defined($hash->{clients}->{$client}->{$_})) ? $hash->{clients}->{$client}->{$_} : '')."\n";
  529. }
  530. $clientData .= "======================================\n";
  531. }
  532. return $clientData;
  533. }
  534. elsif(defined($hash->{clients}->{$getVal})) {
  535. $clientData .= "======================================\n";
  536. for (sort keys %{$hash->{clients}->{$getVal}}) {
  537. $clientData .= "$_ = ".((defined($hash->{clients}->{$getVal}->{$_})) ? $hash->{clients}->{$getVal}->{$_} : '')."\n";
  538. }
  539. $clientData .= "======================================\n";
  540. return $clientData;
  541. }
  542. else {
  543. return "$hash->{NAME}: Unknown client '$getVal' in command '$getName', choose one of: all,$clientNames";
  544. }
  545. }
  546. elsif ($getName eq 'voucherList' && defined $hash->{hotspot}->{vouchers}[0]) {
  547. my $anzahl=0;
  548. my $vouchers = "==================================================================\n";
  549. for my $voucher (@{$hash->{hotspot}->{vouchers}}) {
  550. my $note= '';
  551. if(defined $voucher->{note}){
  552. $note=$voucher->{note};
  553. }
  554. my $gv=$getVal;
  555. $note =~ tr/a-zA-ZÄÖÜäöüß_0-9.,//cd;
  556. $gv =~ tr/a-zA-ZÄÖÜäöüß_0-9.,//cd;
  557. if($gv eq 'all' || ( ($gv =~ /^$note/) && $note ne '')){
  558. for (sort keys %{$voucher}) {
  559. if ($_ !~ /^(_id|admin_name|for_hotspot|qos_overwrite|site_id|create_time)$/) {
  560. $vouchers .= "$_ = ".((defined $voucher->{$_}) ? $voucher->{$_} : '')."\n";
  561. }
  562. }
  563. if(defined $hash->{hotspot}->{voucherCache}->{$note}->{$voucher->{_id}}->{delivered_at}){
  564. $vouchers .= "delivered_at = ".localtime($hash->{hotspot}->{voucherCache}->{$note}->{$voucher->{_id}}->{delivered_at})."\n";
  565. }
  566. $vouchers .= "==================================================================\n";
  567. $anzahl+=1;
  568. }
  569. }
  570. $vouchers .= "Count: ".$anzahl."\n";
  571. return $vouchers;
  572. }
  573. elsif ($getName eq 'voucher' && defined $hash->{hotspot}->{vouchers}[0]) {
  574. my $returnedVoucher = Unifi_getNextVoucherForNote($hash,$getVal);
  575. if ($returnedVoucher eq ""){
  576. return "VoucherCache for $getVal is not defined!";
  577. }
  578. my $returnedVoucherCode = "";
  579. if(defined $returnedVoucher->{_id}){
  580. $returnedVoucherCode = $returnedVoucher->{code};
  581. #if (defined $hash->{hotspot}->{voucherCache}->{$getVal}->{setCmd}){
  582. $hash->{hotspot}->{voucherCache}->{$getVal}->{$returnedVoucher->{_id}}->{delivered_at} = time();
  583. #}
  584. }
  585. return $returnedVoucherCode;
  586. }
  587. elsif( $getName eq 'showAccount' ) {
  588. my $user = $hash->{helper}{username};
  589. my $password = $hash->{helper}{password};
  590. return 'no user set' if( !$user );
  591. return 'no password set' if( !$password );
  592. $user = Unifi_decrypt( $user );
  593. $password = Unifi_decrypt( $password );
  594. return "user: $user\npassword: $password";
  595. }
  596. return undef;
  597. }
  598. ###############################################################################
  599. sub Unifi_Attr(@) {
  600. my ($cmd,$name,$attr_name,$attr_value) = @_;
  601. my $hash = $defs{$name};
  602. if($cmd eq "set") {
  603. if($attr_name eq "disable") {
  604. if($attr_value == 1) {
  605. Unifi_CONNECTED($hash,'disabled');
  606. }
  607. elsif($attr_value == 0 && Unifi_CONNECTED($hash) eq "disabled") {
  608. Unifi_CONNECTED($hash,'initialized');
  609. Unifi_DoUpdate($hash);
  610. }
  611. }
  612. elsif($attr_name eq "devAlias") {
  613. if (!$attr_value) {
  614. CommandDeleteAttr(undef, $name.' '.$attr_name);
  615. return 1;
  616. }
  617. elsif ($attr_value !~ /^([\w\.\-]+:[\w\.\-]+\s?)+$/) {
  618. return "$name: Value \"$attr_value\" is not allowed for devAlias!\n"
  619. ."Must be \"<ID>:<ALIAS> <ID2>:<ALIAS2>\", e.g. 123abc:MyIphone\n"
  620. ."Only these characters are allowed: [alphanumeric - _ .]";
  621. }
  622. }
  623. elsif($attr_name eq "httpLoglevel") {
  624. $hash->{httpParams}->{loglevel} = $attr_value;
  625. }
  626. elsif($attr_name eq "eventPeriod") {
  627. if (!looks_like_number($attr_value) || int($attr_value) < 1 || int($attr_value) > 168) {
  628. return "$name: Value \"$attr_value\" is not allowed.\n"
  629. ."eventPeriod must be a number between 1 and 168."
  630. }
  631. $hash->{unifi}->{eventPeriod} = int($attr_value);
  632. }
  633. elsif($attr_name eq "deprecatedClientNames") {
  634. if (!looks_like_number($attr_value) || int($attr_value) < 0 || int($attr_value) > 1) {
  635. return "$name: Value \"$attr_value\" is not allowed.\n"
  636. ."deprecatedClientNames must be a number between 0 and 1."
  637. }
  638. $hash->{unifi}->{deprecatedClientNames} = int($attr_value);
  639. }
  640. elsif($attr_name eq "voucherCache") {
  641. #ToDo: nächste Zeile entfernen wenn in Unifi_initVoucherCache das Löschen alter Caches implementiert ist
  642. # So löscht man die delivery_at der verbleibenden Caches mit
  643. # Ist aber ja nur ein kurzzeitiges Problem, da die delivery_at eh nach 2 Stunden entfernt werden, daher egal.
  644. $hash->{hotspot}->{voucherCache}=();
  645. $hash->{hotspot}->{voucherCache}->{attr_value} = $attr_value;
  646. return Unifi_initVoucherCache($hash);
  647. }
  648. }
  649. elsif($cmd eq "del") {
  650. if($attr_name eq "disable" && Unifi_CONNECTED($hash) eq "disabled") {
  651. Unifi_CONNECTED($hash,'initialized');
  652. Unifi_DoUpdate($hash);
  653. }
  654. elsif($attr_name eq "httpLoglevel") {
  655. $hash->{httpParams}->{loglevel} = 5;
  656. }
  657. elsif($attr_name eq "eventPeriod") {
  658. $hash->{unifi}->{eventPeriod} = 24;
  659. }
  660. elsif($attr_name eq "deprecatedClientNames") {
  661. $hash->{unifi}->{deprecatedClientNames} = 1;
  662. }
  663. elsif($attr_name eq "voucherCache") {
  664. %{$hash->{hotspot}->{voucherCache}} = ();
  665. }
  666. }
  667. return undef;
  668. }
  669. ###############################################################################
  670. sub Unifi_DoUpdate($@) {
  671. my ($hash,$manual) = @_;
  672. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  673. Log3 $name, 5, "$name ($self) - executed.";
  674. if (Unifi_CONNECTED($hash) eq "disabled") {
  675. Log3 $name, 5, "$name ($self) - Device '$name' is disabled, End now...";
  676. return undef;
  677. }
  678. if (Unifi_CONNECTED($hash)) {
  679. $hash->{unifi}->{updateStartTime} = time();
  680. $hash->{updateDispatch} = { # {updateDispatch}->{callFn}[callFnRef,'receiveFn',receiveFnRef]
  681. Unifi_GetClients_Send => [\&Unifi_GetClients_Send,'Unifi_GetClients_Receive',\&Unifi_GetClients_Receive],
  682. Unifi_GetAccesspoints_Send => [\&Unifi_GetAccesspoints_Send,'Unifi_GetAccesspoints_Receive',\&Unifi_GetAccesspoints_Receive],
  683. Unifi_GetWlans_Send => [\&Unifi_GetWlans_Send,'Unifi_GetWlans_Receive',\&Unifi_GetWlans_Receive],
  684. Unifi_GetVoucherList_Send => [\&Unifi_GetVoucherList_Send,'Unifi_GetVoucherList_Receive',\&Unifi_GetVoucherList_Receive],
  685. Unifi_GetUnarchivedAlerts_Send => [\&Unifi_GetUnarchivedAlerts_Send,'Unifi_GetUnarchivedAlerts_Receive',\&Unifi_GetUnarchivedAlerts_Receive],
  686. Unifi_GetEvents_Send => [\&Unifi_GetEvents_Send,'Unifi_GetEvents_Receive',\&Unifi_GetEvents_Receive],
  687. # Unifi_GetWlanGroups_Send => [\&Unifi_GetWlanGroups_Send,'Unifi_GetWlanGroups_Receive',\&Unifi_GetWlanGroups_Receive],
  688. Unifi_GetHealth_Send => [\&Unifi_GetHealth_Send,'Unifi_GetHealth_Receive',\&Unifi_GetHealth_Receive],
  689. Unifi_ProcessUpdate => [\&Unifi_ProcessUpdate,''],
  690. };
  691. Unifi_NextUpdateFn($hash,$self);
  692. }
  693. else {
  694. Unifi_CONNECTED($hash,'disconnected');
  695. Unifi_Login_Send($hash)
  696. }
  697. return undef;
  698. }
  699. ###############################################################################
  700. sub Unifi_Login_Send($) {
  701. my ($hash) = @_;
  702. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  703. Log3 $name, 5, "$name ($self) - executed.";
  704. my ($loginurl,$logindata);
  705. my $user = $hash->{helper}{username};
  706. my $password = $hash->{helper}{password};
  707. $user = Unifi_decrypt( $user );
  708. $password = Unifi_decrypt( $password );
  709. if($hash->{unifi}->{version} == 3) {
  710. ( $loginurl = $hash->{unifi}->{url} ) =~ s/api\/s.+/login/;
  711. $logindata = "login=login&username=".$user."&password=".$password;
  712. }else {
  713. ( $loginurl = $hash->{unifi}->{url} ) =~ s/api\/s.+/api\/login/;
  714. $logindata = '{"username":"'.$user.'", "password":"'.$password.'"}';
  715. }
  716. HttpUtils_NonblockingGet( {
  717. %{$hash->{httpParams}},
  718. url => $loginurl,
  719. data => $logindata,
  720. callback => \&Unifi_Login_Receive
  721. } );
  722. return undef;
  723. }
  724. sub Unifi_Login_Receive($) {
  725. my ($param, $err, $data) = @_;
  726. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  727. Log3 $name, 5, "$name ($self) - executed.";
  728. if ($err ne "") {
  729. Log3 $name, 5, "$name ($self) - Error while requesting ".$param->{url}." - $err";
  730. }
  731. elsif ($data ne "" && $hash->{unifi}->{version} == 3) {
  732. if ($data =~ /Invalid username or password/si) {
  733. Log3 $name, 1, "$name ($self) - Login Failed! Invalid username or password!";
  734. } else {
  735. Log3 $name, 5, "$name ($self) - Login Failed! Version 3 should not deliver data on successfull login.";
  736. }
  737. }
  738. elsif ($data ne "" || $hash->{unifi}->{version} == 3) { # v3 Login is empty if login is successfully
  739. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401 || ($hash->{unifi}->{version} == 3 && ($param->{code} == 302 || $param->{code} == 200))) {
  740. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  741. if ($hash->{unifi}->{version} == 3 || $data->{meta}->{rc} eq "ok") { # v3 has no rc-state
  742. Log3 $name, 5, "$name ($self) - state=ok || version=3";
  743. $hash->{httpParams}->{header} = '';
  744. for (split("\r\n",$param->{httpheader})) {
  745. if(/^Set-Cookie/) {
  746. s/Set-Cookie:\s(.*?);.*/Cookie: $1/;
  747. $hash->{httpParams}->{header} .= $_.'\r\n';
  748. }
  749. }
  750. if($hash->{httpParams}->{header} ne '') {
  751. $hash->{httpParams}->{header} =~ s/\\r\\n$//;
  752. Log3 $name, 5, "$name ($self) - Login successfully! $hash->{httpParams}->{header}";
  753. Unifi_CONNECTED($hash,'connected');
  754. Unifi_DoUpdate($hash);
  755. return undef;
  756. } else {
  757. $hash->{httpParams}->{header} = undef;
  758. Log3 $name, 5, "$name ($self) - Something went wrong, login seems ok but no cookies received.";
  759. }
  760. }
  761. else {
  762. if (defined($data->{meta}->{msg})) {
  763. if ($data->{meta}->{msg} eq 'api.err.Invalid') {
  764. Log3 $name, 1, "$name ($self) - Login Failed! Invalid username or password!"
  765. ." - state:'$data->{meta}->{rc}' - msg:'$data->{meta}->{msg}'";
  766. } elsif ($data->{meta}->{msg} eq 'api.err.LoginRequired') {
  767. Log3 $name, 1, "$name ($self) - Login Failed! - state:'$data->{meta}->{rc}' - msg:'$data->{meta}->{msg}' -"
  768. ." This error while login indicates that you use wrong <version> or"
  769. ." have to define <version> in your fhem definition.";
  770. } else {
  771. Log3 $name, 5, "$name ($self) - Login Failed! - state:'$data->{meta}->{rc}' - msg:'$data->{meta}->{msg}'";
  772. }
  773. } else {
  774. Log3 $name, 5, "$name ($self) - Login Failed (without msg)! - state:'$data->{meta}->{rc}'";
  775. }
  776. $hash->{httpParams}->{header} = undef;
  777. }
  778. } else {
  779. Log3 $name, 5, "$name ($self) - Failed with HTTP Code $param->{code}!";
  780. }
  781. } else {
  782. Log3 $name, 5, "$name ($self) - Failed because no data was received!";
  783. }
  784. Log3 $name, 5, "$name ($self) - Connect/Login to Unifi-Controller failed. Will try again after interval...";
  785. Unifi_CONNECTED($hash,'disconnected');
  786. InternalTimer(time()+$hash->{unifi}->{interval}, 'Unifi_Login_Send', $hash, 0);
  787. return undef;
  788. }
  789. ###############################################################################
  790. sub Unifi_GetClients_Send($) {
  791. my ($hash) = @_;
  792. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  793. Log3 $name, 5, "$name ($self) - executed.";
  794. HttpUtils_NonblockingGet( {
  795. %{$hash->{httpParams}},
  796. method => "GET",
  797. url => $hash->{unifi}->{url}."stat/sta",
  798. callback => $hash->{updateDispatch}->{$self}[2]
  799. } );
  800. return undef;
  801. }
  802. sub Unifi_GetClients_Receive($) {
  803. my ($param, $err, $data) = @_;
  804. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  805. Log3 $name, 5, "$name ($self) - executed.";
  806. if ($err ne "") {
  807. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  808. }
  809. elsif ($data ne "") {
  810. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  811. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  812. if ($data->{meta}->{rc} eq "ok") {
  813. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  814. $hash->{unifi}->{connectedClients} = undef;
  815. for my $h (@{$data->{data}}) {
  816. $hash->{unifi}->{connectedClients}->{$h->{user_id}} = 1;
  817. $hash->{clients}->{$h->{user_id}} = $h;
  818. }
  819. }
  820. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  821. } else {
  822. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  823. }
  824. }
  825. Unifi_NextUpdateFn($hash,$self);
  826. return undef;
  827. }
  828. ###############################################################################
  829. sub Unifi_GetWlans_Send($) {
  830. my ($hash) = @_;
  831. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  832. Log3 $name, 5, "$name ($self) - executed.";
  833. HttpUtils_NonblockingGet( {
  834. %{$hash->{httpParams}},
  835. method => "GET",
  836. url => $hash->{unifi}->{url}."list/wlanconf",
  837. callback => $hash->{updateDispatch}->{$self}[2],
  838. } );
  839. return undef;
  840. }
  841. sub Unifi_GetWlans_Receive($) {
  842. my ($param, $err, $data) = @_;
  843. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  844. Log3 $name, 5, "$name ($self) - executed.";
  845. if ($err ne "") {
  846. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  847. }
  848. elsif ($data ne "") {
  849. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  850. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  851. if ($data->{meta}->{rc} eq "ok") {
  852. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  853. for my $h (@{$data->{data}}) {
  854. $hash->{wlans}->{$h->{_id}} = $h;
  855. #TODO: Passphrase ggf. verschlüsseln?!
  856. #Ich musste diese Zeile rausnehmen, sonst ist das Json für enable/disableWLAN bei offenem WLAN (ohne Passphrase) falsch
  857. #Aussternen geht nicht, sonst wird das PW unter Umständen darauf geändert.
  858. #$hash->{wlans}->{$h->{_id}}->{x_passphrase} = '***'; # Don't show passphrase in list
  859. delete $hash->{wlans}->{$h->{_id}}->{x_passphrase};
  860. }
  861. }
  862. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  863. } else {
  864. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  865. }
  866. }
  867. Unifi_NextUpdateFn($hash,$self);
  868. return undef;
  869. }
  870. ###############################################################################
  871. sub Unifi_GetHealth_Send($) {
  872. my ($hash) = @_;
  873. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  874. Log3 $name, 5, "$name ($self) - executed.";
  875. HttpUtils_NonblockingGet( {
  876. %{$hash->{httpParams}},
  877. method => "GET",
  878. url => $hash->{unifi}->{url}."stat/health",
  879. callback => $hash->{updateDispatch}->{$self}[2],
  880. } );
  881. return undef;
  882. }
  883. sub Unifi_GetHealth_Receive($) {
  884. my ($param, $err, $data) = @_;
  885. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  886. Log3 $name, 5, "$name ($self) - executed.";
  887. if ($err ne "") {
  888. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  889. }
  890. elsif ($data ne "") {
  891. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  892. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  893. if ($data->{meta}->{rc} eq "ok") {
  894. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  895. for my $h (@{$data->{data}}) {
  896. if (defined($h->{subsystem}) && $h->{subsystem} eq 'wlan') {
  897. $hash->{wlan_health} = $h;
  898. }
  899. }
  900. }
  901. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  902. } else {
  903. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  904. }
  905. }
  906. Unifi_NextUpdateFn($hash,$self);
  907. return undef;
  908. }
  909. ###############################################################################
  910. sub Unifi_GetWlanGroups_Send($) {
  911. my ($hash) = @_;
  912. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  913. Log3 $name, 5, "$name ($self) - executed.";
  914. HttpUtils_NonblockingGet( {
  915. %{$hash->{httpParams}},
  916. method => "GET",
  917. url => $hash->{unifi}->{url}."list/wlangroup",
  918. callback => $hash->{updateDispatch}->{$self}[2],
  919. } );
  920. return undef;
  921. }
  922. sub Unifi_GetWlanGroups_Receive($) {
  923. my ($param, $err, $data) = @_;
  924. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  925. Log3 $name, 5, "$name ($self) - executed.";
  926. if ($err ne "") {
  927. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  928. }
  929. elsif ($data ne "") {
  930. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  931. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  932. if ($data->{meta}->{rc} eq "ok") {
  933. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  934. for my $h (@{$data->{data}}) {
  935. $hash->{wlangroup}->{$h->{_id}} = $h;
  936. }
  937. }
  938. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  939. } else {
  940. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  941. }
  942. }
  943. Unifi_NextUpdateFn($hash,$self);
  944. return undef;
  945. }
  946. ###############################################################################
  947. sub Unifi_GetUnarchivedAlerts_Send($) {
  948. my ($hash) = @_;
  949. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  950. Log3 $name, 5, "$name ($self) - executed.";
  951. HttpUtils_NonblockingGet( {
  952. %{$hash->{httpParams}},
  953. url => $hash->{unifi}->{url}."list/alarm",
  954. callback => $hash->{updateDispatch}->{$self}[2],
  955. data => "{'_sort': '-time', 'archived': false}",
  956. } );
  957. return undef;
  958. }
  959. sub Unifi_GetUnarchivedAlerts_Receive($) {
  960. my ($param, $err, $data) = @_;
  961. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  962. Log3 $name, 5, "$name ($self) - executed.";
  963. if ($err ne "") {
  964. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  965. }
  966. elsif ($data ne "") {
  967. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  968. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  969. if ($data->{meta}->{rc} eq "ok") {
  970. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  971. $hash->{alerts_unarchived} = $data->{data}; #array
  972. }
  973. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  974. } else {
  975. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  976. }
  977. }
  978. Unifi_NextUpdateFn($hash,$self);
  979. return undef;
  980. }
  981. ###############################################################################
  982. sub Unifi_GetEvents_Send($) {
  983. my ($hash) = @_;
  984. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  985. Log3 $name, 5, "$name ($self) - executed.";
  986. HttpUtils_NonblockingGet( {
  987. %{$hash->{httpParams}},
  988. url => $hash->{unifi}->{url}."stat/event",
  989. callback => $hash->{updateDispatch}->{$self}[2],
  990. data => "{'_sort': '-time', 'within': ".$hash->{unifi}->{eventPeriod}."}", # last 24 hours
  991. } );
  992. return undef;
  993. }
  994. sub Unifi_GetEvents_Receive($) {
  995. my ($param, $err, $data) = @_;
  996. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  997. Log3 $name, 5, "$name ($self) - executed.";
  998. if ($err ne "") {
  999. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  1000. }
  1001. elsif ($data ne "") {
  1002. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  1003. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  1004. if ($data->{meta}->{rc} eq "ok") {
  1005. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  1006. $hash->{events} = $data->{data}; #array
  1007. }
  1008. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  1009. } else {
  1010. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  1011. }
  1012. }
  1013. Unifi_NextUpdateFn($hash,$self);
  1014. return undef;
  1015. }
  1016. ###############################################################################
  1017. sub Unifi_GetAccesspoints_Send($) {
  1018. my ($hash) = @_;
  1019. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  1020. Log3 $name, 5, "$name ($self) - executed.";
  1021. HttpUtils_NonblockingGet( {
  1022. %{$hash->{httpParams}},
  1023. url => $hash->{unifi}->{url}."stat/device",
  1024. callback => $hash->{updateDispatch}->{$self}[2],
  1025. data => "{'_depth': 2, 'test': 0}",
  1026. } );
  1027. return undef;
  1028. }
  1029. sub Unifi_GetAccesspoints_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. for my $h (@{$data->{data}}) {
  1042. $hash->{accespoints}->{$h->{_id}} = $h;
  1043. }
  1044. }
  1045. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  1046. } else {
  1047. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  1048. }
  1049. }
  1050. Unifi_NextUpdateFn($hash,$self);
  1051. return undef;
  1052. }
  1053. ###############################################################################
  1054. sub Unifi_ProcessUpdate($) {
  1055. my ($hash) = @_;
  1056. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  1057. Log3 $name, 5, "$name ($self) - executed after ".sprintf('%.4f',time() - $hash->{unifi}->{updateStartTime})." seconds.";
  1058. readingsBeginUpdate($hash);
  1059. #'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''#
  1060. Unifi_SetHealthReadings($hash);
  1061. Unifi_SetClientReadings($hash);
  1062. Unifi_SetAccesspointReadings($hash);
  1063. Unifi_SetWlanReadings($hash);
  1064. Unifi_SetVoucherReadings($hash);
  1065. ## WLANGROUPS ???
  1066. #'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''#
  1067. readingsEndUpdate($hash,1);
  1068. Log3 $name, 5, "$name ($self) - finished after ".sprintf('%.4f',time() - $hash->{unifi}->{updateStartTime})." seconds.";
  1069. InternalTimer(time()+$hash->{unifi}->{interval}, 'Unifi_DoUpdate', $hash, 0);
  1070. return undef;
  1071. }
  1072. ###############################################################################
  1073. sub Unifi_SetClientReadings($) {
  1074. my ($hash) = @_;
  1075. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  1076. Log3 $name, 5, "$name ($self) - executed.";
  1077. my $apNames = {};
  1078. for my $apID (keys %{$hash->{accespoints}}) {
  1079. my $apRef = $hash->{accespoints}->{$apID};
  1080. $apNames->{$apRef->{mac}} = $apRef->{name} ? $apRef->{name} : $apRef->{ip};
  1081. }
  1082. my $ignoreWired = AttrVal($name,"ignoreWiredClients",undef);
  1083. my $ignoreWireless = AttrVal($name,"ignoreWirelessClients",undef);
  1084. my ($apName,$clientName,$clientRef);
  1085. for my $clientID (keys %{$hash->{clients}}) {
  1086. $clientRef = $hash->{clients}->{$clientID};
  1087. $clientName = Unifi_ClientNames($hash,$clientID,'makeAlias');
  1088. next if( $ignoreWired && $clientRef->{is_wired} );
  1089. next if( $ignoreWireless && !$clientRef->{is_wired} );
  1090. $apName = "unknown";
  1091. if ($clientRef->{is_wired}
  1092. && defined $clientRef->{sw_mac} && defined($apNames->{$clientRef->{sw_mac}}) ) {
  1093. $apName = $apNames->{$clientRef->{sw_mac}};
  1094. } elsif (defined $clientRef->{ap_mac} && defined($apNames->{$clientRef->{ap_mac}}) ) {
  1095. $apName = $apNames->{$clientRef->{ap_mac}};
  1096. }
  1097. if (defined $hash->{unifi}->{connectedClients}->{$clientID}) {
  1098. readingsBulkUpdate($hash,$clientName."_hostname",(defined $clientRef->{hostname}) ? $clientRef->{hostname} : (defined $clientRef->{ip}) ? $clientRef->{ip} : 'Unknown');
  1099. readingsBulkUpdate($hash,$clientName."_last_seen",strftime "%Y-%m-%d %H:%M:%S",localtime($clientRef->{last_seen}));
  1100. readingsBulkUpdate($hash,$clientName."_uptime",$clientRef->{uptime});
  1101. readingsBulkUpdate($hash,$clientName."_snr",$clientRef->{rssi});
  1102. readingsBulkUpdate($hash,$clientName."_essid",makeReadingName($clientRef->{essid}));
  1103. readingsBulkUpdate($hash,$clientName."_accesspoint",$apName);
  1104. readingsBulkUpdate($hash,$clientName,'connected');
  1105. }
  1106. elsif (defined($hash->{READINGS}->{$clientName}) && $hash->{READINGS}->{$clientName}->{VAL} ne 'disconnected') {
  1107. Log3 $name, 5, "$name ($self) - Client '$clientName' previously connected is now disconnected.";
  1108. readingsBulkUpdate($hash,$clientName,'disconnected');
  1109. }
  1110. }
  1111. return undef;
  1112. }
  1113. ###############################################################################
  1114. sub Unifi_SetHealthReadings($) {
  1115. my ($hash) = @_;
  1116. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  1117. Log3 $name, 5, "$name ($self) - executed.";
  1118. readingsBulkUpdate($hash,'-UC_wlan_state',$hash->{wlan_health}->{status});
  1119. readingsBulkUpdate($hash,'-UC_wlan_users',$hash->{wlan_health}->{num_user});
  1120. readingsBulkUpdate($hash,'-UC_wlan_accesspoints',$hash->{wlan_health}->{num_ap});
  1121. readingsBulkUpdate($hash,'-UC_wlan_guests',$hash->{wlan_health}->{num_guest});
  1122. readingsBulkUpdate($hash,'-UC_unarchived_alerts',scalar @{$hash->{alerts_unarchived}}) if(ref($hash->{alerts_unarchived}) eq 'ARRAY');
  1123. readingsBulkUpdate($hash,'-UC_events',scalar(@{$hash->{events}}).' (last '.$hash->{unifi}->{eventPeriod}.'h)') if(ref($hash->{events}) eq 'ARRAY');
  1124. return undef;
  1125. }
  1126. ###############################################################################
  1127. sub Unifi_SetAccesspointReadings($) {
  1128. my ($hash) = @_;
  1129. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  1130. Log3 $name, 5, "$name ($self) - executed.";
  1131. my ($apName,$apRef,$essid);
  1132. for my $apID (keys %{$hash->{accespoints}}) {
  1133. $essid = '';
  1134. $apRef = $hash->{accespoints}->{$apID};
  1135. $apName = ($apRef->{name}) ? $apRef->{name} : $apRef->{ip};
  1136. if (defined $apRef->{vap_table} && scalar @{$apRef->{vap_table}}) {
  1137. for my $vap (@{$apRef->{vap_table}}) {
  1138. $essid .= makeReadingName($vap->{essid}).',';
  1139. }
  1140. $essid =~ s/.$//;
  1141. } else {
  1142. my $essid = 'none';
  1143. }
  1144. readingsBulkUpdate($hash,'-AP_'.$apName.'_state',($apRef->{state} == 1) ? 'ok' : 'error');
  1145. readingsBulkUpdate($hash,'-AP_'.$apName.'_clients',$apRef->{'num_sta'});
  1146. if( $apRef->{type} eq 'uap' ) {
  1147. readingsBulkUpdate($hash,'-AP_'.$apName.'_essid',$essid);
  1148. readingsBulkUpdate($hash,'-AP_'.$apName.'_utilizationNA',$apRef->{'na_cu_total'}) if( defined($apRef->{'na_cu_total'}) );
  1149. readingsBulkUpdate($hash,'-AP_'.$apName.'_utilizationNG',$apRef->{'ng_cu_total'}) if( defined($apRef->{'ng_cu_total'}) );
  1150. }
  1151. readingsBulkUpdate($hash,'-AP_'.$apName.'_locate',(!defined $apRef->{locating}) ? 'unknown' : ($apRef->{locating}) ? 'on' : 'off');
  1152. my $poe_power;
  1153. for my $port (@{$apRef->{port_table}}) {
  1154. next if( !$port->{port_poe} );
  1155. $poe_power += $port->{poe_power} if( defined($port->{poe_power}) );
  1156. }
  1157. readingsBulkUpdate($hash,'-AP_'.$apName.'_poePower', $poe_power) if( defined($poe_power) );
  1158. # readingsBulkUpdate($hash,'-AP_'.$apName.'_guests',$apRef->{'guest-num_sta'});
  1159. # readingsBulkUpdate($hash,'-AP_'.$apName.'_users',$apRef->{'user-num_sta'});
  1160. # readingsBulkUpdate($hash,'-AP_'.$apName.'_last_seen',$apRef->{'last_seen'});
  1161. }
  1162. return undef;
  1163. }
  1164. ###############################################################################
  1165. sub Unifi_SetWlanReadings($) {
  1166. my ($hash) = @_;
  1167. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  1168. Log3 $name, 5, "$name ($self) - executed.";
  1169. my ($wlanName,$wlanRef);
  1170. for my $wlanID (keys %{$hash->{wlans}}) {
  1171. $wlanRef = $hash->{wlans}->{$wlanID};
  1172. $wlanName = makeReadingName($wlanRef->{name});
  1173. readingsBulkUpdate($hash,'-WLAN_'.$wlanName.'_state',($wlanRef->{enabled} == JSON::true) ? 'enabled' : 'disabled');
  1174. }
  1175. return undef;
  1176. }
  1177. ###############################################################################
  1178. sub Unifi_SetVoucherReadings($) {
  1179. my ($hash) = @_;
  1180. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  1181. Log3 $name, 5, "$name ($self) - executed.";
  1182. #für jeden Vouchercache den nächsten Vouchercode als Reading anzeigen
  1183. for my $cache (keys %{$hash->{hotspot}->{voucherCache}}) {
  1184. if(ref($hash->{hotspot}->{voucherCache}->{$cache}) eq "HASH"){
  1185. if(defined $hash->{hotspot}->{voucherCache}->{$cache}->{setCmd}){
  1186. my $voucher=Unifi_getNextVoucherForNote($hash,$cache);
  1187. if(ref($voucher) eq "HASH"){
  1188. readingsBulkUpdate($hash,"-VC_".$cache,$voucher->{code});
  1189. }else{
  1190. readingsBulkUpdate($hash,"-VC_".$cache,"-");
  1191. }
  1192. }
  1193. }
  1194. }
  1195. }
  1196. ###############################################################################
  1197. sub Unifi_DisconnectClient_Send($@) {
  1198. my ($hash,@clients) = @_;
  1199. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  1200. Log3 $name, 5, "$name ($self) - executed with count:'".scalar(@clients)."', ID:'".$clients[0]."'";
  1201. my $id = shift @clients;
  1202. HttpUtils_NonblockingGet( {
  1203. %{$hash->{httpParams}},
  1204. url => $hash->{unifi}->{url}."cmd/stamgr",
  1205. callback => \&Unifi_DisconnectClient_Receive,
  1206. clients => [@clients],
  1207. data => "{'mac': '".$hash->{clients}->{$id}->{mac}."', 'cmd': 'kick-sta'}",
  1208. } );
  1209. return undef;
  1210. }
  1211. ###############################################################################
  1212. sub Unifi_DisconnectClient_Receive($) {
  1213. my ($param, $err, $data) = @_;
  1214. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  1215. Log3 $name, 5, "$name ($self) - executed.";
  1216. if ($err ne "") {
  1217. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  1218. }
  1219. elsif ($data ne "") {
  1220. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  1221. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  1222. if ($data->{meta}->{rc} eq "ok") {
  1223. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  1224. }
  1225. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  1226. } else {
  1227. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  1228. }
  1229. }
  1230. if (scalar @{$param->{clients}}) {
  1231. Unifi_DisconnectClient_Send($hash,@{$param->{clients}});
  1232. }
  1233. return undef;
  1234. }
  1235. ###############################################################################
  1236. sub Unifi_UpdateClient_Send($$) {
  1237. my ($hash,$mac) = @_;
  1238. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  1239. Log3 $name, 5, "$name ($self) - executed with mac ".$mac;
  1240. HttpUtils_NonblockingGet( {
  1241. %{$hash->{httpParams}},
  1242. method => "GET",
  1243. url => $hash->{unifi}->{url}."stat/user/".$mac,
  1244. callback => \&Unifi_UpdateClient_Receive
  1245. } );
  1246. return undef;
  1247. }
  1248. ###############################################################################
  1249. sub Unifi_UpdateClient_Receive($) {
  1250. my ($param, $err, $data) = @_;
  1251. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  1252. Log3 $name, 5, "$name ($self) - executed.";
  1253. if ($err ne "") {
  1254. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  1255. }
  1256. elsif ($data ne "") {
  1257. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  1258. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  1259. if ($data->{meta}->{rc} eq "ok") {
  1260. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  1261. my $apNames = {};
  1262. for my $apID (keys %{$hash->{accespoints}}) {
  1263. my $apRef = $hash->{accespoints}->{$apID};
  1264. $apNames->{$apRef->{mac}} = $apRef->{name} ? $apRef->{name} : $apRef->{ip};
  1265. }
  1266. #$hash->{unifi}->{connectedClients} = undef;
  1267. for my $h (@{$data->{data}}) {
  1268. $hash->{unifi}->{connectedClients}->{$h->{user_id}} = 1;
  1269. $hash->{clients}->{$h->{user_id}} = $h;
  1270. readingsBeginUpdate($hash);
  1271. if(defined $h->{user_id}){
  1272. $hash->{unifi}->{connectedClients}->{$h->{user_id}} = $h;
  1273. $hash->{clients}->{$h->{user_id}} = $h;
  1274. my $clientRef = $hash->{clients}->{$h->{user_id}};
  1275. my $clientName = Unifi_ClientNames($hash,$h->{user_id},'makeAlias');
  1276. my $apName = "unknown";
  1277. if ($clientRef->{is_wired}
  1278. && defined $clientRef->{sw_mac} && defined($apNames->{$clientRef->{sw_mac}}) ) {
  1279. $apName = $apNames->{$clientRef->{sw_mac}};
  1280. } elsif (defined $clientRef->{ap_mac} && defined($apNames->{$clientRef->{ap_mac}}) ) {
  1281. $apName = $apNames->{$clientRef->{ap_mac}};
  1282. }
  1283. readingsBulkUpdate($hash,$clientName."_hostname",(defined $clientRef->{hostname}) ? $clientRef->{hostname} : (defined $clientRef->{ip}) ? $clientRef->{ip} : 'Unknown');
  1284. readingsBulkUpdate($hash,$clientName."_last_seen",strftime "%Y-%m-%d %H:%M:%S",localtime($clientRef->{last_seen}));
  1285. readingsBulkUpdate($hash,$clientName."_uptime",$clientRef->{uptime});
  1286. readingsBulkUpdate($hash,$clientName."_snr",$clientRef->{rssi});
  1287. readingsBulkUpdate($hash,$clientName."_accesspoint",$apName);
  1288. readingsBulkUpdate($hash,$clientName,'connected');
  1289. }else{
  1290. Log3 $name, 5, "$name ($self) - Client ".$h->{hostname}." previously connected is now disconnected.";
  1291. readingsBulkUpdate($hash,$h->{hostname},'disconnected');
  1292. }
  1293. readingsEndUpdate($hash,1);
  1294. }
  1295. #$hash->{clients}->{$data->{data}[0]->{user_id}} = $data->{data};
  1296. }
  1297. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  1298. } else {
  1299. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  1300. }
  1301. }
  1302. return undef;
  1303. }
  1304. ###############################################################################
  1305. sub Unifi_BlockClient_Send($@) {
  1306. my ($hash,@clients) = @_;
  1307. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  1308. Log3 $name, 5, "$name ($self) - executed with count:'".scalar(@clients)."', ID:'".$clients[0]."'";
  1309. my $id = shift @clients;
  1310. HttpUtils_NonblockingGet( {
  1311. %{$hash->{httpParams}},
  1312. url => $hash->{unifi}->{url}."cmd/stamgr",
  1313. callback => \&Unifi_BlockClient_Receive,
  1314. clients => [@clients],
  1315. data => "{'mac': '".$hash->{clients}->{$id}->{mac}."', 'cmd': 'block-sta'}",
  1316. } );
  1317. return undef;
  1318. }
  1319. ###############################################################################
  1320. sub Unifi_BlockClient_Receive($) {
  1321. my ($param, $err, $data) = @_;
  1322. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  1323. Log3 $name, 5, "$name ($self) - executed.";
  1324. if ($err ne "") {
  1325. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  1326. }
  1327. elsif ($data ne "") {
  1328. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  1329. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  1330. if ($data->{meta}->{rc} eq "ok") {
  1331. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  1332. }
  1333. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  1334. } else {
  1335. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  1336. }
  1337. }
  1338. if (scalar @{$param->{clients}}) {
  1339. Unifi_BlockClient_Send($hash,@{$param->{clients}});
  1340. }
  1341. return undef;
  1342. }
  1343. ###############################################################################
  1344. sub Unifi_UnblockClient_Send($@) {
  1345. my ($hash,@clients) = @_;
  1346. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  1347. Log3 $name, 5, "$name ($self) - executed with count:'".scalar(@clients)."', ID:'".$clients[0]."'";
  1348. my $id = shift @clients;
  1349. HttpUtils_NonblockingGet( {
  1350. %{$hash->{httpParams}},
  1351. url => $hash->{unifi}->{url}."cmd/stamgr",
  1352. callback => \&Unifi_UnblockClient_Receive,
  1353. clients => [@clients],
  1354. data => "{'mac': '".$hash->{clients}->{$id}->{mac}."', 'cmd': 'unblock-sta'}",
  1355. } );
  1356. return undef;
  1357. }
  1358. ###############################################################################
  1359. sub Unifi_UnblockClient_Receive($) {
  1360. my ($param, $err, $data) = @_;
  1361. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  1362. Log3 $name, 5, "$name ($self) - executed.";
  1363. if ($err ne "") {
  1364. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  1365. }
  1366. elsif ($data ne "") {
  1367. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  1368. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  1369. if ($data->{meta}->{rc} eq "ok") {
  1370. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  1371. }
  1372. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  1373. } else {
  1374. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  1375. }
  1376. }
  1377. if (scalar @{$param->{clients}}) {
  1378. Unifi_UnblockClient_Send($hash,@{$param->{clients}});
  1379. }
  1380. return undef;
  1381. }
  1382. ###############################################################################
  1383. sub Unifi_SwitchSiteLEDs_Send($$) {
  1384. my ($hash,$state) = @_;
  1385. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  1386. Log3 $name, 5, "$name ($self) - executed with command: '".$state."'";
  1387. HttpUtils_NonblockingGet( {
  1388. %{$hash->{httpParams}},
  1389. url => $hash->{unifi}->{url}."set/setting/mgmt",
  1390. callback => \&Unifi_SwitchSiteLEDs_Receive,
  1391. data => "{'led_enabled': ".$state."}",
  1392. } );
  1393. return undef;
  1394. }
  1395. ###############################################################################
  1396. sub Unifi_SwitchSiteLEDs_Receive($) {
  1397. my ($param, $err, $data) = @_;
  1398. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  1399. Log3 $name, 5, "$name ($self) - executed.";
  1400. if ($err ne "") {
  1401. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  1402. }
  1403. elsif ($data ne "") {
  1404. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  1405. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  1406. if ($data->{meta}->{rc} eq "ok") {
  1407. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  1408. }
  1409. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  1410. } else {
  1411. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  1412. }
  1413. }
  1414. return undef;
  1415. }
  1416. ###############################################################################
  1417. sub Unifi_WlanconfRest_Send($$@) {
  1418. my ($hash,$id,$data) = @_;
  1419. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  1420. my $json = encode_json( $data );
  1421. Log3 $name, 5, "$name ($self) - executed with $json.";
  1422. HttpUtils_NonblockingGet( {
  1423. %{$hash->{httpParams}},
  1424. method => "PUT",
  1425. url => $hash->{unifi}->{url}."rest/wlanconf/".$id,
  1426. callback => \&Unifi_WlanconfRest_Receive,
  1427. aps => [],
  1428. data => $json,
  1429. } );
  1430. return undef;
  1431. }
  1432. sub Unifi_WlanconfRest_Receive($) {
  1433. my ($param, $err, $data) = @_;
  1434. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  1435. Log3 $name, 3, "$name ($self) - executed.";
  1436. if ($err ne "") {
  1437. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  1438. }
  1439. elsif ($data ne "") {
  1440. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  1441. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  1442. } else {
  1443. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  1444. }
  1445. }
  1446. return undef;
  1447. }
  1448. ###############################################################################
  1449. sub Unifi_GetVoucherList_Send($) {
  1450. my ($hash) = @_;
  1451. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  1452. Log3 $name, 5, "$name ($self) - executed.";
  1453. HttpUtils_NonblockingGet( {
  1454. %{$hash->{httpParams}},
  1455. method => "GET",
  1456. url => $hash->{unifi}->{url}."stat/voucher",
  1457. callback => \&Unifi_GetVoucherList_Receive,
  1458. } );
  1459. return undef;
  1460. }
  1461. #######################################
  1462. sub Unifi_GetVoucherList_Receive($) {
  1463. my ($param, $err, $data) = @_;
  1464. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  1465. Log3 $name, 5, "$name ($self) - executed.";
  1466. if ($err ne "") {
  1467. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  1468. }
  1469. elsif ($data ne "") {
  1470. my $dataString=$data;
  1471. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  1472. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  1473. if ($data->{meta}->{rc} eq "ok") {
  1474. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  1475. $hash->{hotspot}->{vouchers} = $data->{data}; #array
  1476. }
  1477. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  1478. } else {
  1479. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  1480. }
  1481. # VoucherCache bereinigen um bereits verwendete / zu lange gecachte Voucher
  1482. my $cachetime=time() - 2 * 60 * 60; #Maximal zwei Stunden
  1483. for my $cache (keys %{$hash->{hotspot}->{voucherCache}}) {
  1484. my $expand=0;
  1485. if(ref($hash->{hotspot}->{voucherCache}->{$cache}) eq "HASH"){
  1486. for my $voucher (keys %{$hash->{hotspot}->{voucherCache}->{$cache}}) {
  1487. if(ref($hash->{hotspot}->{voucherCache}->{$cache}->{$voucher}) eq "HASH" && defined $hash->{hotspot}->{voucherCache}->{$cache}->{$voucher}->{delivered_at}){
  1488. if($hash->{hotspot}->{voucherCache}->{$cache}->{$voucher}->{delivered_at} lt $cachetime){
  1489. delete $hash->{hotspot}->{voucherCache}->{$cache}->{$voucher};
  1490. }
  1491. }
  1492. }
  1493. #wenn Cache zu leer neue Voucher anlegen
  1494. if($expand==0){ #Der Unifi-Controller mag es nicht, wenn man kurz hintereinander zwei requests sendet, daher gleich mehrere auf einmal
  1495. my $minSize=$hash->{hotspot}->{voucherCache}->{$cache}->{minSize};
  1496. my $aktSize=$dataString =~ s/"note" : "$cache"//g;
  1497. if(defined $minSize && $aktSize<$minSize){
  1498. my $setCmd=$hash->{hotspot}->{voucherCache}->{$cache}->{setCmd};
  1499. my @words=split("[ \t][ \t]*", $setCmd);
  1500. my %params=("expire"=>$words[0],"n"=>$words[1],"quota"=>$words[2],"note"=>$words[3]);
  1501. Log3 $name, 3, "$name ($self) - expand VoucherCache ($cache).";
  1502. Unifi_CreateVoucher_Send($hash, %params);
  1503. $expand=1;
  1504. }
  1505. }
  1506. }
  1507. }
  1508. }
  1509. Unifi_NextUpdateFn($hash,$self);
  1510. return undef;
  1511. }
  1512. ###############################################################################
  1513. sub Unifi_CreateVoucher_Send($%) {
  1514. my ($hash,%a)=@_;
  1515. my $expire = $a{"expire"};
  1516. my $n = $a{"n"};
  1517. my $quota = $a{"quota"};
  1518. my $note = $a{"note"};
  1519. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  1520. Log3 $name, 5, "$name ($self) - executed. expire: ".$expire." - n: ".$n." - quota: ".$quota." - note: ".$note." - ".%a;
  1521. HttpUtils_NonblockingGet( {
  1522. %{$hash->{httpParams}},
  1523. url => $hash->{unifi}->{url}."cmd/hotspot",
  1524. callback => \&Unifi_CreateVoucher_Receive,
  1525. data => "{'cmd': 'create-voucher', 'expire': '".$expire."', 'n': '".$n."', 'quota': '".$quota."', 'note': '".$note."'}",
  1526. } );
  1527. return undef;
  1528. }
  1529. #######################################
  1530. sub Unifi_CreateVoucher_Receive($) {
  1531. my ($param, $err, $data) = @_;
  1532. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  1533. Log3 $name, 3, "$name ($self) - executed.";
  1534. if ($err ne "") {
  1535. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  1536. }
  1537. elsif ($data ne "") {
  1538. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  1539. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  1540. } else {
  1541. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  1542. }
  1543. }
  1544. # der Voucher ist im Unifi-Modul dann erst mit dem nächsten Update enthalten.
  1545. return undef;
  1546. }
  1547. ###############################################################################
  1548. sub Unifi_ApCmd_Send($$@) { #cmd: 'set-locate', 'unset-locate', 'restart'
  1549. my ($hash,$cmd,@aps) = @_;
  1550. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  1551. Log3 $name, 5, "$name ($self) - executed with cmd:'".$cmd."', count:'".scalar(@aps)."', ID:'".$aps[0]."'";
  1552. my $id = shift @aps;
  1553. HttpUtils_NonblockingGet( {
  1554. %{$hash->{httpParams}},
  1555. url => $hash->{unifi}->{url}."cmd/devmgr",
  1556. callback => \&Unifi_ApCmd_Receive,
  1557. aps => [@aps],
  1558. cmd => $cmd,
  1559. data => "{'mac': '".$hash->{accespoints}->{$id}->{mac}."', 'cmd': '".$cmd."'}",
  1560. } );
  1561. return undef;
  1562. }
  1563. sub Unifi_ApJson_Send($$) {
  1564. my ($hash,$data) = @_;
  1565. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  1566. my $json = encode_json( $data );
  1567. Log3 $name, 5, "$name ($self) - executed with $json.";
  1568. HttpUtils_NonblockingGet( {
  1569. %{$hash->{httpParams}},
  1570. url => $hash->{unifi}->{url}."cmd/devmgr",
  1571. callback => \&Unifi_ApCmd_Receive,
  1572. aps => [],
  1573. data => $json,
  1574. } );
  1575. return undef;
  1576. }
  1577. sub Unifi_RestJson_Send($$$) {
  1578. my ($hash,$id,$data) = @_;
  1579. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  1580. my $json = encode_json( $data );
  1581. Log3 $name, 5, "$name ($self) - executed with $json.";
  1582. HttpUtils_NonblockingGet( {
  1583. %{$hash->{httpParams}},
  1584. method => "PUT",
  1585. url => $hash->{unifi}->{url}."rest/device/".$id,
  1586. callback => \&Unifi_ApCmd_Receive,
  1587. aps => [],
  1588. data => $json,
  1589. } );
  1590. return undef;
  1591. }
  1592. ###############################################################################
  1593. sub Unifi_ApCmd_Receive($) {
  1594. my ($param, $err, $data) = @_;
  1595. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  1596. Log3 $name, 5, "$name ($self) - executed.";
  1597. if ($err ne "") {
  1598. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  1599. }
  1600. elsif ($data ne "") {
  1601. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  1602. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  1603. if ($data->{meta}->{rc} eq "ok") {
  1604. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  1605. }
  1606. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  1607. } else {
  1608. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  1609. }
  1610. }
  1611. if (scalar @{$param->{aps}}) {
  1612. Unifi_ApCmd_Send($hash,$param->{cmd},@{$param->{aps}});
  1613. }
  1614. return undef;
  1615. }
  1616. ###############################################################################
  1617. sub Unifi_ArchiveAlerts_Send($) {
  1618. my ($hash) = @_;
  1619. my ($name,$self) = ($hash->{NAME},Unifi_Whoami());
  1620. Log3 $name, 5, "$name ($self) - executed.";
  1621. HttpUtils_NonblockingGet( {
  1622. %{$hash->{httpParams}},
  1623. url => $hash->{unifi}->{url}."cmd/evtmgr",
  1624. callback => \&Unifi_Cmd_Receive,
  1625. data => "{'cmd': 'archive-all-alarms'}",
  1626. } );
  1627. return undef;
  1628. }
  1629. ###############################################################################
  1630. sub Unifi_Cmd_Receive($) {
  1631. my ($param, $err, $data) = @_;
  1632. my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash});
  1633. Log3 $name, 5, "$name ($self) - executed.";
  1634. if ($err ne "") {
  1635. Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"});
  1636. }
  1637. elsif ($data ne "") {
  1638. if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) {
  1639. eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; };
  1640. if ($data->{meta}->{rc} eq "ok") {
  1641. Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'";
  1642. }
  1643. else { Unifi_ReceiveFailure($hash,$data->{meta}); }
  1644. } else {
  1645. Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."});
  1646. }
  1647. }
  1648. return undef;
  1649. }
  1650. ###############################################################################
  1651. sub Unifi_ClientNames($@) {
  1652. my ($hash,$ID,$W) = @_;
  1653. my $clientRef;
  1654. my $devAliases = AttrVal($hash->{NAME},"devAlias",0);
  1655. if(defined $ID && defined $W && $W eq 'makeAlias') { # Return Alias from ID
  1656. $clientRef = $hash->{clients}->{$ID};
  1657. if (defined $hash->{unifi}->{deprecatedClientNames} && $hash->{unifi}->{deprecatedClientNames} eq 0){
  1658. my $goodName="";
  1659. $goodName=makeReadingName($clientRef->{name}) if defined $clientRef->{name};
  1660. my $goodHostname="";
  1661. $goodHostname=makeReadingName($clientRef->{hostname}) if defined $clientRef->{hostname};
  1662. if ( ($devAliases && $devAliases =~ /$ID:(.+?)(\s|$)/)
  1663. || ($devAliases && defined $clientRef->{name} && $devAliases =~ /$goodName:(.+?)(\s|$)/)
  1664. || ($devAliases && defined $clientRef->{hostname} && $devAliases =~ /$goodHostname:(.+?)(\s|$)/)
  1665. || ($goodName =~ /(.+)/)
  1666. || ($goodHostname =~ /(.+)/)
  1667. ) {
  1668. $ID = $1;
  1669. }
  1670. }elsif ( ($devAliases && $devAliases =~ /$ID:(.+?)(\s|$)/)
  1671. || ($devAliases && defined $clientRef->{name} && $devAliases =~ /$clientRef->{name}:(.+?)(\s|$)/)
  1672. || ($devAliases && defined $clientRef->{hostname} && $devAliases =~ /$clientRef->{hostname}:(.+?)(\s|$)/)
  1673. || (defined $clientRef->{name} && $clientRef->{name} =~ /^([\w\.\-]+)$/)
  1674. || (defined $clientRef->{hostname} && $clientRef->{hostname} =~ /^([\w\.\-]+)$/)
  1675. ) {
  1676. $ID = $1;
  1677. }
  1678. return $ID;
  1679. }
  1680. elsif (defined $ID && defined $W && $W eq 'makeID') { # Return ID from Alias
  1681. for my $clientID (keys %{$hash->{clients}}) {
  1682. $clientRef = $hash->{clients}->{$clientID};
  1683. my $goodName=makeReadingName($clientRef->{name}) if defined $clientRef->{name};
  1684. my $goodHostname=makeReadingName($clientRef->{hostname}) if defined $clientRef->{hostname};
  1685. if ( ($devAliases && $devAliases =~ /$clientID:$ID/)
  1686. || ($devAliases && defined $clientRef->{name} && ($devAliases =~ /$clientRef->{name}:$ID/ || $devAliases =~ /$goodName:$ID/) )
  1687. || ($devAliases && defined $clientRef->{hostname} && ($devAliases =~ /$clientRef->{hostname}:$ID/ || $devAliases =~ /$goodHostname:$ID/) )
  1688. || (defined $clientRef->{name} && ($clientRef->{name} eq $ID || $goodName eq $ID) )
  1689. || (defined $clientRef->{hostname} && ($clientRef->{hostname} eq $ID || $goodHostname eq $ID) )
  1690. ) {
  1691. $ID = $clientID;
  1692. last;
  1693. }
  1694. }
  1695. return $ID;
  1696. }
  1697. else { # Return all clients in a scalar
  1698. my $clients = '';
  1699. for my $clientID (keys %{$hash->{clients}}) {
  1700. $clients .= Unifi_ClientNames($hash,$clientID,'makeAlias').',';
  1701. }
  1702. $clients =~ s/.$//;
  1703. return $clients;
  1704. }
  1705. }
  1706. ###############################################################################
  1707. sub Unifi_SSIDs($@){
  1708. my ($hash,$ID,$W) = @_;
  1709. my $wlanRef;
  1710. if(defined $ID && defined $W && $W eq 'makeName') { # Return Name from ID
  1711. $wlanRef = $hash->{wlans}->{$ID};
  1712. if (defined $wlanRef->{name} ){ #&& $wlanRef->{name} =~ /^([\w\.\-]+)$/) {
  1713. $ID = makeReadingName($wlanRef->{name});
  1714. }
  1715. return $ID;
  1716. }
  1717. elsif (defined $ID && defined $W && $W eq 'makeID') { # Return ID from Name
  1718. for (keys %{$hash->{wlans}}) {
  1719. $wlanRef = $hash->{wlans}->{$_};
  1720. if (defined $wlanRef->{name} && makeReadingName($wlanRef->{name}) eq $ID) {
  1721. $ID = $_;
  1722. last;
  1723. }
  1724. }
  1725. return $ID;
  1726. }
  1727. else { # Return all wlans in a scalar
  1728. my $wlans = '';
  1729. for my $wlanID (keys %{$hash->{wlans}}) {
  1730. $wlans .= Unifi_SSIDs($hash,$wlanID,'makeName').',';
  1731. }
  1732. $wlans =~ s/.$//;
  1733. return $wlans;
  1734. }
  1735. }
  1736. ###############################################################################
  1737. sub Unifi_ApNames($@) {
  1738. my ($hash,$ID,$W) = @_;
  1739. my $apRef;
  1740. if(defined $ID && defined $W && $W eq 'makeName') { # Return Name or IP from ID
  1741. $apRef = $hash->{accespoints}->{$ID};
  1742. if ( (defined $apRef->{name} && $apRef->{name} =~ /^([\w\.\-]+)$/)
  1743. || (defined $apRef->{ip} && $apRef->{ip} =~ /^([\w\.\-]+)$/)
  1744. ) {
  1745. $ID = $1;
  1746. }
  1747. return $ID;
  1748. }
  1749. elsif (defined $ID && defined $W && $W eq 'makeID') { # Return ID from Name or IP
  1750. for (keys %{$hash->{accespoints}}) {
  1751. $apRef = $hash->{accespoints}->{$_};
  1752. if ( (defined $apRef->{name} && $apRef->{name} eq $ID)
  1753. || (defined $apRef->{ip} && $apRef->{ip} eq $ID)
  1754. ) {
  1755. $ID = $_;
  1756. last;
  1757. }
  1758. }
  1759. return $ID;
  1760. }
  1761. else { # Return all aps in a scalar
  1762. my $aps = '';
  1763. for my $apID (keys %{$hash->{accespoints}}) {
  1764. $aps .= Unifi_ApNames($hash,$apID,'makeName').',';
  1765. }
  1766. $aps =~ s/.$//;
  1767. return $aps;
  1768. }
  1769. }
  1770. ###############################################################################
  1771. sub Unifi_initVoucherCache($){
  1772. my ($hash) = @_;
  1773. my @voucherCaches=split(/,/, $hash->{hotspot}->{voucherCache}->{attr_value});
  1774. my @notes=();
  1775. foreach(@voucherCaches){
  1776. my $voucherCache=$_;
  1777. my @words=split("[ \t][ \t]*", $voucherCache);
  1778. if (scalar(@words) !=4){
  1779. return "$hash->{NAME} voucherCache: Four arguments per cache needed!."
  1780. }
  1781. if (!looks_like_number($words[0]) || int($words[0]) < 1 ||
  1782. !looks_like_number($words[1]) || int($words[1]) < 1 ||
  1783. !looks_like_number($words[2]) || int($words[2]) < 1
  1784. ) {
  1785. return "$hash->{NAME} voucherCache: First three arguments (expire, n, quota) must be numeric."
  1786. }
  1787. my $note=$words[3];
  1788. push(@notes,$note);
  1789. $hash->{hotspot}->{voucherCache}->{$note}->{setCmd} = $voucherCache;
  1790. $hash->{hotspot}->{voucherCache}->{$note}->{minSize} = $words[1];
  1791. }
  1792. #ToDo: Löschen nicht mehr verwendeter Caches
  1793. # dazu iterieren über $hash->{hotspot}->{voucherCache}
  1794. # immer wenn es darin setCmd gibt ist oder war es ein Cache, ansonsten ist es attr_value
  1795. # wenn $hash->{hotspot}->{voucherCache}->{$note} nicht in @notes, dann löschen
  1796. return undef;
  1797. }
  1798. ###############################################################################
  1799. sub Unifi_getNextVoucherForNote($$){
  1800. my ($hash,$getVal)=@_;
  1801. my $deliverytime=time();
  1802. my $returnedVoucher="";
  1803. for my $voucher (@{$hash->{hotspot}->{vouchers}}) {
  1804. my $note= '';
  1805. if(defined $voucher->{note}){
  1806. $note=$voucher->{note};
  1807. }
  1808. my $gv=$getVal;
  1809. $note =~ tr/a-zA-ZÄÖÜäöüß_0-9.,//cd;
  1810. $gv =~ tr/a-zA-ZÄÖÜäöüß_0-9.,//cd;
  1811. if($gv eq 'all' || ( ($gv =~ /^$note/) && $note ne '')){
  1812. if(! defined $hash->{hotspot}->{voucherCache}->{$getVal}->{$voucher->{_id}}->{delivered_at}){
  1813. $returnedVoucher=$voucher;
  1814. last;
  1815. }else{
  1816. if($hash->{hotspot}->{voucherCache}->{$getVal}->{$voucher->{_id}}->{delivered_at} < $deliverytime){
  1817. $deliverytime=$hash->{hotspot}->{voucherCache}->{$getVal}->{$voucher->{_id}}->{delivered_at};
  1818. $returnedVoucher=$voucher;
  1819. }
  1820. }
  1821. }
  1822. }
  1823. return $returnedVoucher;
  1824. }
  1825. ###############################################################################
  1826. sub Unifi_NextUpdateFn($$) {
  1827. my ($hash,$fn) = @_;
  1828. my $NextUpdateFn = 0;
  1829. for (keys %{$hash->{updateDispatch}}) { # {updateDispatch}->{callFn}[callFnRef,'receiveFn',receiveFnRef]
  1830. if($hash->{updateDispatch}->{$_}[1] && $hash->{updateDispatch}->{$_}[1] eq $fn) {
  1831. delete $hash->{updateDispatch}->{$_};
  1832. } elsif(!$NextUpdateFn && $hash->{updateDispatch}->{$_}[0] && $_ ne 'Unifi_ProcessUpdate') {
  1833. $NextUpdateFn = $hash->{updateDispatch}->{$_}[0];
  1834. }
  1835. }
  1836. if (!$NextUpdateFn && $hash->{updateDispatch}->{Unifi_ProcessUpdate}[0]) {
  1837. $NextUpdateFn = $hash->{updateDispatch}->{Unifi_ProcessUpdate}[0];
  1838. delete $hash->{updateDispatch}->{Unifi_ProcessUpdate};
  1839. }
  1840. $NextUpdateFn->($hash) if($NextUpdateFn);
  1841. return undef;
  1842. }
  1843. ###############################################################################
  1844. sub Unifi_ReceiveFailure($$) {
  1845. my ($hash,$meta) = @_;
  1846. my ($name,$self) = ($hash->{NAME},Unifi_Whowasi());
  1847. if (defined $meta->{msg}) {
  1848. if ($meta->{msg} eq 'api.err.LoginRequired') {
  1849. Log3 $name, 5, "$name ($self) - LoginRequired detected...";
  1850. if(Unifi_CONNECTED($hash)) {
  1851. Log3 $name, 5, "$name ($self) - I am the first who detected LoginRequired. Do re-login...";
  1852. Unifi_CONNECTED($hash,'disconnected');
  1853. Unifi_DoUpdate($hash);
  1854. return undef;
  1855. }
  1856. }
  1857. elsif ($meta->{msg} eq "api.err.NoSiteContext" || ($hash->{unifi}->{version} == 3 && $meta->{msg} eq "api.err.InvalidObject")) {
  1858. Log3 $name, 1, "$name ($self) - Failed! - state:'$meta->{rc}' - msg:'$meta->{msg}'"
  1859. ." - This error indicates that the <siteID> in your definition is wrong."
  1860. ." Try to modify your definition with <sideID> = default.";
  1861. }
  1862. else {
  1863. Log3 $name, 5, "$name ($self) - Failed! - state:'$meta->{rc}' - msg:'$meta->{msg}'";
  1864. }
  1865. } else {
  1866. Log3 $name, 5, "$name ($self) - Failed (without message)! - state:'$meta->{rc}'";
  1867. }
  1868. }
  1869. ###############################################################################
  1870. sub Unifi_CONNECTED($@) {
  1871. my ($hash,$set) = @_;
  1872. if ($set) {
  1873. $hash->{unifi}->{CONNECTED} = $set;
  1874. RemoveInternalTimer($hash);
  1875. %{$hash->{updateDispatch}} = ();
  1876. if (!defined($hash->{READINGS}->{state}->{VAL}) || $hash->{READINGS}->{state}->{VAL} ne $set) {
  1877. readingsSingleUpdate($hash,"state",$set,1);
  1878. }
  1879. return undef;
  1880. }
  1881. else {
  1882. if ($hash->{unifi}->{CONNECTED} eq 'disabled') {
  1883. return 'disabled';
  1884. }
  1885. elsif ($hash->{unifi}->{CONNECTED} eq 'connected') {
  1886. return 1;
  1887. } else {
  1888. return 0;
  1889. }
  1890. }
  1891. }
  1892. ###############################################################################
  1893. sub
  1894. Unifi_encrypt($)
  1895. {
  1896. my ($decoded) = @_;
  1897. my $key = getUniqueId();
  1898. my $encoded;
  1899. return $decoded if( $decoded =~ /^crypt:(.*)/ );
  1900. for my $char (split //, $decoded) {
  1901. my $encode = chop($key);
  1902. $encoded .= sprintf("%.2x",ord($char)^ord($encode));
  1903. $key = $encode.$key;
  1904. }
  1905. return 'crypt:'. $encoded;
  1906. }
  1907. sub
  1908. Unifi_decrypt($)
  1909. {
  1910. my ($encoded) = @_;
  1911. my $key = getUniqueId();
  1912. my $decoded;
  1913. $encoded = $1 if( $encoded =~ /^crypt:(.*)/ );
  1914. for my $char (map { pack('C', hex($_)) } ($encoded =~ /(..)/g)) {
  1915. my $decode = chop($key);
  1916. $decoded .= chr(ord($char)^ord($decode));
  1917. $key = $decode.$key;
  1918. }
  1919. return $decoded;
  1920. }
  1921. ###############################################################################
  1922. sub Unifi_Whoami() { return (split('::',(caller(1))[3]))[1] || ''; }
  1923. sub Unifi_Whowasi() { return (split('::',(caller(2))[3]))[1] || ''; }
  1924. ###############################################################################
  1925. 1;
  1926. =pod
  1927. =item device
  1928. =item summary Interpret / control of Ubiquiti Networks UniFi-controller
  1929. =item summary_DE Auswertung / Steuerung eines Ubiquiti Networks UniFi-Controller
  1930. =begin html
  1931. <a name="Unifi"></a>
  1932. <h3>Unifi</h3>
  1933. <ul>
  1934. Unifi is the FHEM module for the Ubiquiti Networks (UBNT) - Unifi Controller.<br>
  1935. <br>
  1936. 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>
  1937. 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>
  1938. 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>
  1939. <br>
  1940. Or you can use the other readings or set and get features to control your unifi-controller, accesspoints and wlan-clients.
  1941. <br>
  1942. <h4>Prerequisites</h4>
  1943. <ul>
  1944. The Perl module JSON is required. <br>
  1945. On Debian/Raspbian: <code>apt-get install libjson-perl </code><br>
  1946. Via CPAN: <code>cpan install JSON</code>
  1947. </ul>
  1948. <h4>Define</h4>
  1949. <ul>
  1950. <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>
  1951. <br><br>
  1952. <br>
  1953. &lt;name&gt;:
  1954. <ul>
  1955. <code>The FHEM device name for the device.</code><br>
  1956. </ul>
  1957. &lt;ip&gt;:
  1958. <ul>
  1959. <code>The ip of your unifi-controller.</code><br>
  1960. </ul>
  1961. &lt;port&gt;:
  1962. <ul>
  1963. <code>The port of your unifi-controller. Normally it's 8443 or 443.</code><br>
  1964. </ul>
  1965. &lt;username&gt;:
  1966. <ul>
  1967. <code>The Username to log on.</code><br>
  1968. </ul>
  1969. &lt;password&gt;:
  1970. <ul>
  1971. <code>The password to log on.</code><br>
  1972. </ul>
  1973. [&lt;interval&gt;]:
  1974. <ul>
  1975. <code>(optional without &lt;siteID&gt; and &lt;version&gt;)<br>
  1976. Interval to fetch the information from the unifi-api. <br>
  1977. default: 30 seconds</code><br>
  1978. </ul>
  1979. [&lt;siteID&gt;]:
  1980. <ul>
  1981. <code>(optional without &lt;version&gt;)<br>
  1982. You can find the site-ID by selecting the site in the UniFi web interface.<br>
  1983. e.g. https://192.168.12.13:8443/manage/s/foobar the siteId you must use is: foobar.<br>
  1984. default: default</code><br>
  1985. </ul>
  1986. [&lt;version&gt;]:
  1987. <ul>
  1988. <code>(optional if you use unifi v4)<br>
  1989. Unifi-controller version. <br>
  1990. Version must be specified if version is not 4. At the moment version 3 and 4 are supported.<br>
  1991. default: 4</code><br>
  1992. </ul> <br>
  1993. </ul>
  1994. <h4>Examples</h4>
  1995. <ul>
  1996. <code>define my_unifi_controller Unifi 192.168.1.15 443 admin secret</code><br>
  1997. <br>
  1998. Or with optional parameters &lt;interval&gt;, &lt;siteID&gt; and &lt;version&gt;:<br>
  1999. <code>define my_unifi_controller Unifi 192.168.1.15 443 admin secret 30 default 3</code><br>
  2000. </ul>
  2001. <h4>Set</h4>
  2002. <ul>
  2003. <code>Note: Some setters are not available if controller is not connected, or no data is available for them.</code><br>
  2004. <br>
  2005. <li><code>set &lt;name&gt; update</code><br>
  2006. Makes immediately a manual update. </li>
  2007. <br>
  2008. <li><code>set &lt;name&gt; updateClient &lt;mac&gt;</code><br>
  2009. Makes immediately a manual update of the client specified by MAC-Adress. </li>
  2010. <br>
  2011. <li><code>set &lt;name&gt; clear &lt;readings|clientData|voucherCache|all&gt;</code><br>
  2012. Clears the readings, clientData, voucherCache or all. </li>
  2013. <br>
  2014. <li><code>set &lt;name&gt; archiveAlerts</code><br>
  2015. Archive all unarchived Alerts. </li>
  2016. <br>
  2017. <li><code>set &lt;name&gt; disconnectClient &lt;all|user_id|controllerAlias|hostname|devAlias&gt;</code><br>
  2018. Disconnect one ore all clients. </li>
  2019. <br>
  2020. <li><code>set &lt;name&gt; restartAP &lt;all|_id|name|ip&gt;</code><br>
  2021. Restart one ore all accesspoints. </li>
  2022. <br>
  2023. <li><code>set &lt;name&gt; setLocateAP &lt;all|_id|name|ip&gt;</code><br>
  2024. Start 'locate' on one or all accesspoints. </li>
  2025. <br>
  2026. <li><code>set &lt;name&gt; unsetLocateAP &lt;all|_id|name|ip&gt;</code><br>
  2027. Stop 'locate' on one or all accesspoints. </li>
  2028. <li><code>set &lt;name&gt; poeMode &lt;name|mac|id&gt; &lt;port&gt; &lt;off|auto|passive|passthrough|restart&gt;</code><br>
  2029. Set PoE mode for &lt;port&gt;. </li>
  2030. <li><code>set &lt;name&gt; blockClient &lt;clientname&gt;</code><br>
  2031. Block the &lt;clientname&gt;</li>
  2032. <li><code>set &lt;name&gt; unblockClient &lt;clientname&gt;</code><br>
  2033. Unblocks the &lt;clientname&gt;</li>
  2034. <li><code>set &lt;name&gt; disableWLAN &lt;ssid&gt;</code><br>
  2035. Disables WLAN with &lt;ssid&gt;</li>
  2036. <li><code>set &lt;name&gt; enableWLAN &lt;ssid&gt;</code><br>
  2037. Enables WLAN with &lt;ssid&gt;</li>
  2038. <li><code>set &lt;name&gt; switchSiteLEDs &lt;on|off&gt;</code><br>
  2039. Enables or disables the Status-LED settings of the site.</li>
  2040. <li><code>set &lt;name&gt; createVoucher &lt;expire&gt; &lt;n&gt; &lt;quota&gt; &lt;note&gt;</code><br>
  2041. Creates &lt;n&gt; vouchers that expires after &lt;expire&gt; minutes, are usable &lt;quota&gt;-times with a &lt;note&gt;no spaces in note allowed</li>
  2042. </ul>
  2043. <h4>Get</h4>
  2044. <ul>
  2045. <code>Note: Some getters are not available if no data is available for them.</code><br>
  2046. <br>
  2047. <li><code>get &lt;name&gt; clientData &lt;all|user_id|controllerAlias|hostname|devAlias&gt;</code><br>
  2048. Show more details about clients.</li>
  2049. <br>
  2050. <li><code>get &lt;name&gt; events</code><br>
  2051. Show events in specified 'eventPeriod'.</li>
  2052. <br>
  2053. <li><code>get &lt;name&gt; unarchivedAlerts</code><br>
  2054. Show all unarchived Alerts.</li>
  2055. <li><code>get &lt;name&gt; poeState [name|mac|id]</code><br>
  2056. Show port PoE state.</li>
  2057. <li><code>get &lt;name&gt; voucher [note]</code><br>
  2058. Show next voucher-code with specified note. If &lt;note&gt; is used in voucherCache the voucher will be marked as delivered</li>
  2059. <li><code>get &lt;name&gt; voucherList [all|note]</code><br>
  2060. Show list of vouchers (all or with specified note only).</li>
  2061. <li><code>get &lt;name&gt; showAccount</code><br>
  2062. Show decrypted user and passwort.</li>
  2063. </ul>
  2064. <h4>Attributes</h4>
  2065. <ul>
  2066. <li>attr devAlias<br>
  2067. Can be used to rename device names in the format <code>&lt;user_id|controllerAlias|hostname&gt;:Aliasname.</code><br>
  2068. Separate using blank to rename multiple devices.<br>
  2069. Example (user_id):<code> attr unifi devAlias 5537d138e4b033c1832c5c84:iPhone-Claudiu</code><br>
  2070. Example (controllerAlias):<code> attr unifi devAlias iPhoneControllerAlias:iPhone-Claudiu</code><br>
  2071. Example (hostname):<code> attr unifi devAlias iphone:iPhone-Claudiu</code><br></li>
  2072. <br>
  2073. <li>attr eventPeriod &lt;1...168&gt;<br>
  2074. Can be used to configure the time-period (hours) of fetched events from controller.<br>
  2075. <code>default: 24</code></li>
  2076. <br>
  2077. <li>attr disable &lt;1|0&gt;<br>
  2078. With this attribute you can disable the whole module. <br>
  2079. If set to 1 the module will be stopped and no updates are performed.<br>
  2080. If set to 0 the automatic updating will performed.</li>
  2081. <br>
  2082. <li>attr ignoreWiredClients &lt;1|0&gt;<br>
  2083. With this attribute you can disable readings for wired clients. <br>
  2084. If set to 1 readings for wired clients are not generated.<br>
  2085. If set to 0 or not defined, readings for wired clients will be generated.</li>
  2086. <br>
  2087. <li>attr ignoreWirelessClients &lt;1|0&gt;<br>
  2088. With this attribute you can disable readings for wireless clients. <br>
  2089. If set to 1 readings for wireless clients are not generated.<br>
  2090. If set to 0 or not defined, readings for wireless clients will be generated.</li>
  2091. <br>
  2092. <li>attr <a href="#verbose">verbose</a> 5<br>
  2093. This attribute will help you if something does not work as espected.</li>
  2094. <br>
  2095. <li>attr httpLoglevel <1,2,3,4,5><br>
  2096. Can be used to debug the HttpUtils-Module. Set it smaller or equal as your 'global verbose level'.<br>
  2097. <code>default: 5</code></li>
  2098. <br>
  2099. <li>attr deprecatedClientNames <0,1><br>
  2100. Client-names in reading-names, reading-values and drop-down-lists can be set in two ways. Both ways generate the client-name in follwing order: 1. Attribute devAlias; 2. client-alias in Unifi;3. hostname;4. internal unifi-id.<br>
  2101. 1: Deprecated. Valid characters for unifi-client-alias or hostname are [a-z][A-Z][0-9][-][.]<br>
  2102. 0: All invalid characters are replaced by using makeReadingName() in fhem.pl.<br>
  2103. <code>default: 1 (if module is defined and/or attribute is not set)</code></li>
  2104. <br>
  2105. <li>attr voucherCache &lt;expire n quota note, ...&gt;<br>
  2106. Define voucher-cache(s). Comma separeted list of four parameters that are separated by spaces; no spaces in note!.<br>
  2107. By calling <code>get voucher &lt;note&gt;</code> the delivery-time of the voucher will be saved in the cache.
  2108. The voucher with the oldest delivery-time will be returned by <code>get voucher &lt;note&gt;</code>.
  2109. If the voucher is not used for 2 hours, the delivery-time in the cache will be deleted.<br>
  2110. <code>e.g.: 120 2 1 2h,180 5 2 3h</code> defines two caches.<br>
  2111. The first cache has a min size of 2 vouchers. The vouchers expire after 120 minutes and can be used one-time.<br>
  2112. The second cache has a min size of 5 vouchers. The vouchers expire after 180 minutes and can be used two-times.</li>
  2113. <br>
  2114. <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  2115. </ul>
  2116. <h4>Readings</h4>
  2117. <ul>
  2118. Note: All readings generate events. You can control this with <a href="#readingFnAttributes">these global attributes</a>.
  2119. <li>Each client has 7 readings for connection-state, SNR, uptime, last_seen-time, connected-AP, essid and hostname.</li>
  2120. <li>Each AP has 3 readings for state (can be 'ok' or 'error'), essid's and count of connected-clients.</li>
  2121. <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>
  2122. <li>The Unifi-device reading 'state' represents the connection-state to the unifi-controller (can be 'connected', 'disconnected', 'initialized' and 'disabled').</li>
  2123. <li>Each voucher-cache has a reading with the next free voucher code.</li>
  2124. </ul>
  2125. <br>
  2126. </ul>
  2127. =end html
  2128. =cut