74_Unifi.pm 103 KB

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