74_Unifi.pm 104 KB

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