From e2d4e46a2b51f02380517c682c9422dc93d85104 Mon Sep 17 00:00:00 2001 From: PatrickR Date: Sun, 13 Aug 2017 12:17:41 +0000 Subject: [PATCH] lepresenced: updated to V0.82 lepresenced: lepresenced-0.82-1.deb added. git-svn-id: https://svn.fhem.de/fhem/trunk@14889 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- .../PRESENCE/deb/lepresenced-0.82-1.deb | Bin 0 -> 6962 bytes fhem/contrib/PRESENCE/lepresenced | 582 +++++++----------- 2 files changed, 223 insertions(+), 359 deletions(-) create mode 100644 fhem/contrib/PRESENCE/deb/lepresenced-0.82-1.deb diff --git a/fhem/contrib/PRESENCE/deb/lepresenced-0.82-1.deb b/fhem/contrib/PRESENCE/deb/lepresenced-0.82-1.deb new file mode 100644 index 0000000000000000000000000000000000000000..11f8882db9d0d60c467062af5b2b2cea6d553a32 GIT binary patch literal 6962 zcmai(RZtuXlZ6L|;5N9s4{m{AA-I#^n&8gh5Znpw8eD?}A7F5Iw}A-+3+@nf@BM3c zYv1-%S6B7JdHbrms}J2UDQK_DRk0b1IB>c3_7hL0Qc#?sjd?B?vi4K{bQp_y|w`NvS=iRlLotpbNc5CYlt}*`Nvfyn@_m?jjqD4KD_9Er$t`Tgfr~8K8XaAQ^-Who3labMI9%R zf9~kNn;H~lhd*!Hln>-KF`Wde0C*%6FCuhG1JG^R;uww$I)_nJ|1i(cF0yQRbtdA} zbx;_#2to)eZu-6_P|X3^Hc@yEfq^E%uBG5rCvx7Dwfl4F>>eZEIXi6Y3RR}c>+_;Q zZ~d)3!3`rw&wH)yY7l<8f^f$u+Cd zN1Z}b?_e(l`_J&3f?eb1ga@SBS4;0FR%4qKh*DGwTj_A7j@D*n8bQX-A6dO+GVs-Top_)7RC^=~v6;Zj(OY1Bp?;a{}KmijF~Q|H%EV z&-{oX&o|CF+-7i`pA;&bg3bhSyhEgx|EE#S@icrVqy?}70H4jl=KsOS`yY({(f>!H z5Z}K%{wuRL&%(ZX1^@u^-cpl1a!(N`=p2C#0r(%d0I5#Y>c6~RNy&`?B$%C)0SIY5 zR7#RQ#C;RY#rhx$6^!Md`j~Uu2oOa}oZ}DQajo;6WFL3aRZV7je@@52!YKMuvp=X$ zv8WjU5rNAQtR0V9#$c5jQN?(?S%OO2`mf*1%WPU6el7L(V`fBSre}b5<5hmJBi{w+ zn5e|V9vtLUMb*fEWiQHqrM@Zt1m$-0CvI9pnPW0BRj?Hm5{WL{j7y&elQWc0Xyr0L zj;@iGn;zyshJ)4ERkJ3uxj!msG#X1Xy~lo(%a%woVS(?Pz0Z{c(YVWXf>AAO9cc-H|F*R~{Blz*>l!!93#Fgs29|O3W zY`F_1UBia2*RiC$#vR+h3zz#VKh{&BsJbs$)Az`c-n60j)1JNhB2CFXi#{4>fnZWo zMvtA&{3&i5i+0fPpU2aN;xFe?1yzIRD8PYF@miJ7jaoE;C2R zs8n0OiiS3&4e)Sw<#Ef>g(?vWFeV;oSLFh6%X~A3mBDjAlRQ7npCCTb21TY(NF~Jty)4`i6(_ zN=OOGPa!YL_m8=zD?s!3abH!-l zSb$>6S7WSP$J^u{S22+VnvPA-DJa1zIv+2Bvt^V=))I;47rDKZa$p&?Zp-oPatQin4rkY`Ybj>i#05*Qa5yy%58%)uKh9(+Sc}RTzBk{G%sHQEcRn8hGvuRvbqdVaUHT zd&Ii@Ku4j6Fm_<{HyCFW{Pqb+EdVew>yrzlNN7~H#Cjo+H^gk#+nj{Z$Lk1=_WsFl zSYkw3vE?Ird3sR6I!;aXj$KH-Ku*&@)I78S2-2 zJqusXmP?8vD}BNpw%%UX0}Gf%#WZHZBxnmsvHQ$5j*U4jm8sADtAra0o(ARN%uWh< z3hq6hRx#=A0>uLnck;f6u78+Oe<+J~3gG7apgkcQv{8L*b$fsppC9_2I>aT^OoDg} zi-QGK6A&kflW>#At^{Ed7_TvVEIQe!AmhvhrGJsGeseOINPDX#$1?(8JMt*@|^YVNbSGXEE2@7dktahwKIH_XoNLYkhMwnxCfh+-*Dz!jSg zo{EI{-CRctw-A&6_t_zm{4ds*(_}ljxR;zZ{h{~jW%>Q;q{{$to7zn^JPLQPG%+f2 zS*O0!r{*8!SSMG1!-y<9ml#`hWyXqU1{CHI@W#@@MvD&m`x%S`e`8q?zi8Yb!L>G- zV}Xl~Y>z@HReEfA^?$B&v~xl$x-%st*>{AOO05FHb?3@qt#Kje-Qd`-qbUZP8_#EI z#9yahCWKFoFARmhS<1>G{~(L-rAPvIAJwXMts$is1Ce^*R=RQmC)hK4l7LDj6Ws2`b=LYaS8&Rpk=`0?GAA#gJx>;UvE;E4oW z;F8}7GUU1sG{RxCpLt^u5o$0ieeU|)BL0s0J64AM8G{igZXVpBL>jBC0q;Fu;%%h0 zb)mCKuX0!iicT6>T^MIkgB;RCcrg6fE{%&QiRQzlFxaYhrU2CML33uC%5Nol3B-#& zfa3|lG}t6~=_Y1UkYSgy4u)Eu5%GJIFlJ7-(EHZ>>S&hE(26tg^6;k) zZUKDDc(o&7TM59d?Z&b+nzt9}9Q&RK=#$I<<@x=Lwyt@fGDDeR4F@3wP5>8_B%v}s zta0t%+BkSldZ9T@K8j73JZ`>Klw_eQSXSusjK!5%UXb!tlGCA|?DY5GW}N)ZZ%>Na z?e8qyv;a`2&k*WE&!Km7+3a(-gfMmj(9(%wZ?0(LZQ;@5U|2+pg2reB=!pL}l4;=u z_Ov|)MEjuiHvN(nC+k%-$D}t4^e5W+76_)ayj0VjG~Ba=JrP{xY5r zOZou&RS%jefVk0}PcG>BE;%GfyxXGbt0Qudrnl+3@RqL>|D(&F{7 ztpEjlS)5TX)8Po#H35Bp$iSw9!W38~U@X@?+3 z7_#<9K^iVf^(p$%jmqI}kcej{k@rQ(ys_@A^#;<{hlZ47-R4m1rp>Md1SP7lk7Uy_ zIsI=CT~kyi)TB7TDlb|^?;@&hns-j#)66RfLGoW=@nAL;lv>^w#T2LtDsAbt5;9jZqt))NA6yWfHi<$!z3SB5lPDcD71YOPbzlw6Sv(~ zlw>%0y_8vfvcD1WOSc1x_qjJ3wy?qKIZhlU z-}bJ7^)y<_>xyN!h3AYl4WM)(E!3r0Zo@&3ck+zxM#A0i_Z!pDzzZ!6#JDx3V0ea* zbGkAdYetJx$}TbB`~g^1l?75~r>)TD45D@jks+KMw;H4QWQ>)C*zLC9aP@?Nt!U2* zoM((?Ok-X1w;)rsh!R%P)gl69@Yj}cz+MKjoH|Ys^;>Yl#&^D6-e0HNz>TgneM!IF zfRAIYYDWIL=VYuiN{1(q&GdSC=S+hO`m}DsvHuFvx{z%W?zx7-=Edi+zJ|ZbmiM@; z%!>9Q32+PC`?+{`kvLsu2`9p4zu{4+>}%h869f73K;=^$NGHJv#`2ESol2*FQs3dE zAE(%RXoSLYH}?6=>WF^se8t(#E>fYyKKvQ3u@gdfd}1z z%^OM<+qM?hpUoTxH%Y`GfT386+*Ff1W?y)VF2GC5(5|(1BOd7Nj3%Yk1oc+<%LsTrAo3>n|JrENLe_OUGNs9Cz8GSh zDcKeng0Gi#w*TFzzr6=Hb<0-nDJ(Zt*>0r$+X+|as2915x#OeDZtU2I%!s6!c)n5? zy6ty|T@Qv zi3!p7QqMRHm}P=Q$bye3&q496{E>{8Z&5shHW$+h7RVhYsa&kYwmXZ&jiGnJ##y(nw=%K_r;+rLg}tbg~}))+_>ol@NIBT#e%06)1>s zvI1P*@$GA|I*J(Db-MLZp)-*q&C?2$HD%*O*JJZ*FN_KNykBCOMp0AbsK8%kVI*vb zBFj%-R2%{-A~OPr1SLTmJN1ca#hGqbElyzWd47Bt{d!mJ9o6|2BwFh7Bllq5FGwQU z`={}@wqKh(?p@7H@f1Jy(Ysu63lx`tQBZBypm8!GIKm4s=Pf53^__mq^~HM6A_iTAOM$Q*Q^L9eNA*&T95nGmsIagUBSamWUy)RuF zjQ2X=Yb5D*=g7oh5J4O*_Sx56`8b*cd$hG{q~vGK>yu3sZ$`*B=-T}h6{9?5@xBSI zrm9GcRf&h*&r=lBB3W=jFl3ZsR26(5^EA@rjJh;8lH|>+Na`ZPA!C_KE9Kj)I;D7GQWJH(?WE2OjS4!5qVe(F zEJIVT#dp=OAMQl7H{z|N<2QPo%kqR=#Z|4Qjrhrq{Af}J@yvcu_c6y6M`MNx;!}|% z@h)8*FWKC@u^ka+>`MlIA?Lo`4!wO3O0+W1r$IcM!Gj&A0L#^^KzS5zMw$2c**TSf zpGi4}Z$TW*7IC9KrllX`A2|htb=){JRa_Mk?_Gh^*T00zlg8QoM7)+1jvYfFML(3k zz0sL~TlGJoh9On3_!-lZ#S@DX;C8Y*5DW139BPTO5{i9UU7UggGZGDd&^h3~p}gk*eU3r-k!3KJ_iS zPVJ??U%7W-?2p)atMRM{^zs*csBrBY5vWW>zpl?Z>S;po#d?RpEEFvtiZA@wtzy0I z|JiDIOttfHtJs3ek#+eJ-!vj1!Y!qYCDC+-%PlZU-ldnjuQN)A1J|Clk<=|S>t5LE zl?p+642ZAyJJGLbBTzNC+N|q9lyXro8WW2;jZtwrRS-H(8$$C?nlO6u`V%S}}%;cqEEZ#z=gJt?m6Z;o8TK!))=+`iFV(lgcUSXb@p6m@qKJho|<)_DXE|Ak9;-_5pa7V`=JpsC&)ckgV<@>He zX#U95cPbu-%o0xhiOFk9kkg{w$mEM7%ffY)27=R}ufAvr;#TuOX6;;&j=K><79JKLlE98Ti#VLQ~(tNsC)rFgdDy zmS%Z`F!%3!asY3Al=&3C#sFEXY;iUDIw=5hI+~f$*enM9*6W@gn*PRizDR*XiS=OJ zUp-7>Aw(jw=O^~gOYCwmhiKSVC1+-D*&%T{1+`tD>jpUM^JBo&c_D>z#&ZpfHVZD?vd(Fx%RrmM&=HrJ+mEJ8fti>2Qc$ z=xe>0SSswPV$L68GV{&My$Xcc*5QaNI#ha!7WN!C5`D)ys{t1O8k;<$Le^zAo)!Jz zb@z%H30o9ZwR{X^|aV*m{SSjbFs&|V?y6b zvAkO>yjEt`6f!0X`wnwVV`eeLk9F0+@^p2bf5SOnZ(#zS0HaLTMn zdRUmM)Ivf@=n+p69Y`^?2?6a)``?sAA|>EFiy2L%WH|{yZ4ptW^eJ?boTY`5?h|wD zsj}<-pu|(D>@(zBLIx4E*^xSwXMD`sb}XB}G}qn-9!bc&%jmK9yLD_lw%6jnMTH9= z-kU4Da{=xERGVN~*{biCe*>hedFNg!5u!dg=##RbRUuLe49_-FnDf_c`g>gp>36Ma zj#1kX=Obq^;;ugxAVF51wLVYj&a;E@HCA9DsiC2iExej(NFIJMdn#(o6{@P+q>K{O zpWtvT#7U)oq;vsvfCeFc!4=1j=SlyDPBV93BA$Q#%_2CVF{GY13^&Vj4?Sz{3tXJ2 zxi!k#FlKL!Ma2hKz#3ccNHSzNB~+@utHz}S^4WoD70a9$iPVjFsHxao$W~j=aT}e` zvr3@0wAvz5TmW}x-4(L*fM~GNCQTKozKk&Bmwe_w>g_}Iy&7xw8t%WXP~MWBx8l7q zjxk>jaU;tO{J5hz?6br2EtDE}SNU8ld#a0%j7{xJljZIYQ|J7JZo`A}c@mk@*kruA zR?3b5s|Yk5_nKPRWbFfQg2LnbSAwP@k}No3qW&S6TCZz0h3R&FuJOLo4wL`a;Wb|K zw>aG^XB+ifD^|%fAo?l8PqU&VpSxg$#tvn58xrYTrU+>`*QVKcY!kB7dsBP$z>c4_ zr}k%yn#FI4F4GIBTFE-zgmy|YwTWlhWXW@I&@KjyUt-mvpg59V#3gP}d+UDwwX0hB z-DCIfO6O#8u$pvFIzxtQ=7-C+op- zqbLp=Y4+FVfQ{>sDbg)R(h z+FS0r!N{1IxwTr3?x2#h{+?^sW<0YJoK=nVndq7=_mZ8F6a)dL z$*6Ukd$5!1w9!oj2g#Fx0`bW*mFvi$-)-31BdF;?x z5`j?Smsbq!*QV&M5=sW??0^ithS(munXh;CrV-FqK_9XdZfmQiHV-;!qI`$o^xxz; zObujbW7}A1q&btvv?i{j#Bg10%F9L!a--Q$PRNM(=;Ew?T%P^q;T&piUqkSR@5{Eq zTi&S-7qOI-5d&Xbxf)8C4JfFjna4_2NsOj0B3Mg`$8_GhS8x-`Fp(dv0C&ks`-8AG zNW)4Gi|Td?Ch*Q+294I);|BxFI5u7R@(Phm0YedC{gBB}0;bu2K7|gQbwQKiSG;9j zUB=5sLW(qPZeeD7&ecDbvqw+}{+JO~c3ZRj6BQ^SO~0_+AaVf^p#SCvfa^m(q=o_O NN5p?)1Y~`K{{jddIvM}~ literal 0 HcmV?d00001 diff --git a/fhem/contrib/PRESENCE/lepresenced b/fhem/contrib/PRESENCE/lepresenced index e01692f0c..8c4f8a16c 100755 --- a/fhem/contrib/PRESENCE/lepresenced +++ b/fhem/contrib/PRESENCE/lepresenced @@ -1,31 +1,31 @@ #!/usr/bin/perl ############################################################################## -# $Id$ +# $Id$ ############################################################################## # -# lepresenced +# lepresenced # -# checks for one or multiple bluetooth *low energy* devices for their -# presence state and reports it to the 73_PRESENCE.pm module. +# checks for one or multiple bluetooth *low energy* devices for their +# presence state and reports it to the 73_PRESENCE.pm module. # -# Copyright (C) 2015-2016 P. Reinhardt, pr-fhem (at) reinhardtweb (dot) de +# Copyright (C) 2015-2016 P. Reinhardt, pr-fhem (at) reinhardtweb (dot) de # -# This script free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. +# This script free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. # -# The GNU General Public License can be found at -# http://www.gnu.org/copyleft/gpl.html. -# A copy is found in the textfile GPL.txt and important notices to the -# license from the author is found in LICENSE.txt distributed with these -# scripts. +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# A copy is found in the textfile GPL.txt and important notices to the +# license from the author is found in LICENSE.txt distributed with these +# scripts. # -# This script is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# This script is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # ############################################################################## @@ -43,397 +43,261 @@ use Sys::Syslog qw(:standard :macros); use Time::HiRes qw(usleep); use Net::Server::Daemonize qw(daemonize); -use constant RETRY_SLEEP => 1; -use constant INET_RECV_BUFFER => 1024; -use constant MAINLOOP_SLEEP_US => 250 * 1000; +use constant RETRY_SLEEP => 1; +use constant INET_RECV_BUFFER => 1024; +use constant MAINLOOP_SLEEP_US => 100 * 1000; -use constant CLEANUP_INTERVAL => 15 * 60; -use constant CLEANUP_MAX_AGE => 30 * 60; -use constant STATS_INTERVAL => 5 * 60; +use constant CLEANUP_INTERVAL => 15 * 60; +use constant CLEANUP_MAX_AGE => 30 * 60; +use constant STATS_INTERVAL => 5 * 60; -use constant DEFAULT_RSSI_THRESHOLD => 10; -use constant RSSI_WINDOW => 10; +use constant ME => 'lepresenced'; +use constant VERSION => '0.6'; -use constant ME => 'lepresenced'; -use constant VERSION => '0.81'; - -use constant PIDFILE => '/var/run/' . ME . '.pid'; - -use constant { - HCIDUMP_STATE_NONE => 0, - HCIDUMP_STATE_LE_META_EVENT => 1, - HCIDUMP_STATE_LE_ADVERTISING_REPORT => 2, - HCIDUMP_STATE_ADV_INT => 3, - HCIDUMP_STATE_SCAN_RSP => 4, -}; +use constant PIDFILE => '/var/run/' . ME . '.pid'; my %devices :shared; my @clients = (); my $syslog_level; sub syslogw { - return if (scalar(@_) < 2); - if (scalar(@_)==2) { - my ($priority, $message) = @_; - syslog($priority, "[tid:%i] %s: $message", threads->self()->tid(), (caller(1))[3] // 'main') if ($syslog_level >= $priority); - } else { - my ($priority, $format, @args) = @_; - syslog($priority, "[tid:%i] %s: $format", threads->self()->tid(), (caller(1))[3] // 'main', @args) if ($syslog_level >= $priority); - } + return if (scalar(@_) < 2); + if (scalar(@_)==2) { + my ($priority, $message) = @_; + syslog($priority, "[tid:%i] %s: $message", threads->self()->tid(), (caller(1))[3] // 'main') if ($syslog_level >= $priority); + } else { + my ($priority, $format, @args) = @_; + syslog($priority, "[tid:%i] %s: $format", threads->self()->tid(), (caller(1))[3] // 'main', @args) if ($syslog_level >= $priority); + } } sub error_exit { - my $exit_code = shift(); - syslogw(LOG_ERR, @_); - foreach my $thread (threads->list()) { - $thread->exit(0); - } - exit ($exit_code); + my $exit_code = shift(); + syslogw(LOG_ERR, @_); + foreach my $thread (threads->list()) { + $thread->exit(0); + } + exit ($exit_code); } sub usage_exit() { - print("usage:\n"); - printf("\t%s --bluetoothdevice --listenaddress --listenport --loglevel --daemon\n", ME); - printf("\t%s -b -a -p -l -d\n", ME); - print("valid log levels:\n"); - print("\tLOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG. Default: LOG_INFO\n"); - print("optional arguments:\n"); - print("--legacymode - legacy mode without rssi detection. Use if you do not have hcidump installed.\n"); - printf("--rssithreshold - rssi deviation to trigger an update. Minimum value: 5, default: %s\n", DEFAULT_RSSI_THRESHOLD); - print("examples:\n"); - printf("\t%s --bluetoothdevice hci0 --listenaddress 127.0.0.1 --listenport 5333 --daemon\n", ME); - printf("\t%s --loglevel LOG_DEBUG --daemon\n", ME); - closelog(); - exit(1); + print("usage:\n"); + printf("\t%s --bluetoothdevice --listenaddress --listenport --loglevel --daemon\n", ME); + printf("\t%s -b -a -p -l -d\n", ME); + print("valid log levels:\n"); + print("\tLOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG. Default: LOG_INFO\n"); + print("examples:\n"); + printf("\t%s --bluetoothdevice hci0 --listenaddress 127.0.0.1 --listenport 5333 --daemon\n", ME); + printf("\t%s --loglevel LOG_DEBUG --daemon\n", ME); + closelog(); + exit(1); } sub parse_options() { - my $device = "hci0"; - my $daemonize = 0; - my $listen_address = "0.0.0.0"; - my $listen_port = "5333"; - my $syslog_level = "LOG_INFO"; - my $legacy_mode = 0; - my $rssi_threshold = DEFAULT_RSSI_THRESHOLD; - - GetOptions( - 'bluetoothdevice|device|b=s' => \$device, - 'daemon|daemonize|d!' => \$daemonize, - 'listenaddress|address|a=s' => \$listen_address, - 'listenport|port|p=i' => \$listen_port, - 'loglevel|l=s' => \$syslog_level, - 'legacymode|legacy!' => \$legacy_mode, - 'rssithreshold=i' => \$rssi_threshold, - ) or usage_exit(); - - usage_exit() if ($rssi_threshold < 5); - - $listen_address =~ m/^\d+\.\d+\.\d+\.\d+$/ or usage_exit(); - $syslog_level =~ m/^LOG_(EMERG|ALERT|CRIT|ERR|WARNING|NOTICE|INFO|DEBUG)$/ or usage_exit(); - $syslog_level = eval($syslog_level); - - return ($device, $daemonize, $listen_address, $listen_port, $syslog_level, $legacy_mode, $rssi_threshold); + my $device = "hci0"; + my $daemonize = 0; + my $listen_address = "0.0.0.0"; + my $listen_port = "5333"; + my $syslog_level = "LOG_INFO"; + + GetOptions( + 'bluetoothdevice|device|b=s' => \$device, + 'daemon|daemonize|d!' => \$daemonize, + 'listenaddress|address|a=s' => \$listen_address, + 'listenport|port|p=i' => \$listen_port, + 'loglevel|l=s' => \$syslog_level + ) or usage_exit(); + $listen_address =~ m/^\d+\.\d+\.\d+\.\d+$/ or usage_exit(); + $syslog_level =~ m/^LOG_(EMERG|ALERT|CRIT|ERR|WARNING|NOTICE|INFO|DEBUG)$/ or usage_exit(); + $syslog_level = eval($syslog_level); + + return ($device, $daemonize, $listen_address, $listen_port, $syslog_level); } -sub update_device($$$) { - my ($mac, $name, $rssi) = @_; - $mac = lc($mac); - { - lock(%devices); - unless (exists $devices{$mac}) { - my %device :shared; - $devices{$mac} = \%device; - } - $name = '(unknown)' if ($name eq ''); - if (!defined($devices{$mac}{'name'}) || $name ne '(unknown)') { - $devices{$mac}{'name'} = $name - } - $devices{$mac}{'rssi'} = $rssi; - $devices{$mac}{'reported_rssi'} = $rssi if (!defined($devices{$mac}{'reported_rssi'})); - $devices{$mac}{'prevtimestamp'} = $devices{$mac}{'timestamp'}; - $devices{$mac}{'timestamp'} = time(); - } - #dump_devices(); +sub update_device($$) { + my ($mac, $name) = @_; + $mac = lc($mac); + { + lock(%devices); + unless (exists $devices{$mac}) { + my %device :shared; + $devices{$mac} = \%device; + } + $devices{$mac}{'name'} = $name unless ($name eq '(unknown)' && defined($devices{$mac}{'name'})); + $devices{$mac}{'timestamp'} = time(); + } } -sub dump_devices() { - foreach my $mac (keys(%devices)) { - printf("mac: %s, timestamp: %s, rssi: %s, name: %s\n", $mac, $devices{$mac}{'timestamp'}, $devices{$mac}{'rssi'}, $devices{$mac}{'name'}); - } - print("\n"); -} - -sub bluetooth_scan_thread($$) { - my ($device, $legacy_mode) = @_; - my $hcitool; - for(;;) { - my $pid = open($hcitool, "-|", "stdbuf -oL hcitool -i " . $device . " lescan --duplicates 2>&1") || die('Unable to start scanning. Please make sure hcitool and stdbuf are installed!'); - while (<$hcitool>) { - chomp($_); - if ($_ eq 'LE Scan ...') { - syslogw(LOG_INFO, "Received '%s'.", $_); - } elsif (my ($fbmac, $fbname) = $_ =~ /^([\da-f]{2}:[\da-f]{2}:[\da-f]{2}:[\da-f]{2}:[\da-f]{2}:[\da-f]{2})\s(.*)$/i) { - if ($legacy_mode) { - #syslogw(LOG_DEBUG, "Received advertisement from bluetooth mac address '%s' with name '%s'.", $fbmac, $fbname); - update_device($fbmac, $fbname, 'unknown'); - } - } elsif ( - $_ =~ m/^Set scan parameters failed: Input\/output error$/ || - $_ =~ m/^Invalid device: Network is down$/ - ) { - syslogw(LOG_WARNING, "Received '%s', resetting...", $_); - system(sprintf('hciconfig %s reset', $device)); - } else { - syslogw(LOG_WARNING, "Received unknown output: '%s'!", $_); - } - } - syslogw(LOG_WARNING, "hcitool exited, retrying..."); - close($hcitool); - sleep(RETRY_SLEEP); - } -} - -sub bluetooth_dump_thread($) { - my ($device) = @_; - my $hcidump; - my %rssitable; - - for(;;) { - my $pid = open($hcidump, "-|", "hcidump -i " . $device) || die('Unable to start scanning. Please make sure hcidump is installed or use legacy mode (--legacymode)!'); - my $state = HCIDUMP_STATE_NONE; - my $current_mac = ''; - my $current_rssi = ''; - my $current_name = ''; - - while (<$hcidump>) { - chomp($_); - if ($_ =~ m/^>/) { - if ($current_mac) { - #printf("DEBUG: mac: %s, name: '%s', rssi: %s\n", $current_mac, $current_name, $current_rssi); - - # update rssi queue - unless (exists $rssitable{$current_mac}) { - $rssitable{$current_mac} = []; - } - if ($current_rssi) { - shift(@{$rssitable{$current_mac}}) if(scalar(@{$rssitable{$current_mac}}) >= RSSI_WINDOW); - push(@{$rssitable{$current_mac}}, $current_rssi); - } - my $mean_rssi = 0; - foreach my $rssi (@{$rssitable{$current_mac}}) { - $mean_rssi += $rssi; - } - $mean_rssi = int($mean_rssi / scalar(@{$rssitable{$current_mac}})); - #printf("DEBUG: mac: %s, rssi count: %i, rssis: %s, mean: %s\n", $current_mac, scalar(@{$rssitable{$current_mac}}), join(',', @{$rssitable{$current_mac}}), $mean_rssi); - - update_device($current_mac, $current_name, $mean_rssi); - } - $current_mac = ''; - $current_rssi = ''; - $current_name = ''; - if ($_ =~ m/^> HCI Event: LE Meta Event \(0x3e\) plen \d+$/) { - $state = HCIDUMP_STATE_LE_META_EVENT; - } else { - $state = HCIDUMP_STATE_NONE; - } - } elsif ( - $state == HCIDUMP_STATE_LE_META_EVENT && - $_ eq ' LE Advertising Report' - ) { - $state = HCIDUMP_STATE_LE_ADVERTISING_REPORT; - } elsif ($state == HCIDUMP_STATE_LE_ADVERTISING_REPORT) { - if ( - $_ eq ' ADV_IND - Connectable undirected advertising (0)' || - $_ eq ' ADV_NONCONN_IND - Non connectable undirected advertising (3)' - ) { - $state = HCIDUMP_STATE_ADV_INT; - } elsif ($_ eq ' SCAN_RSP - Scan Response (4)') { - $state = HCIDUMP_STATE_SCAN_RSP; - } - } elsif ($state == HCIDUMP_STATE_SCAN_RSP || $state == HCIDUMP_STATE_ADV_INT) { - if ($_ =~ m/^ bdaddr ([0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}) \((Public|Random)\)$/) { - $current_mac = $1; - } elsif ($_ =~ m/^ Complete local name: '(.*)'$/) { - $current_name = $1; - } elsif ($_ =~ m/^ RSSI: (-\d+)$/) { - $current_rssi = $1; - } - } - } - syslogw(LOG_WARNING, "hcidump exited, retrying..."); - close($hcidump); - sleep(RETRY_SLEEP); - } +sub bluetooth_thread($) { + my ($device) = @_; + my $hcitool; + for(;;) { + my $pid = open($hcitool, "-|", "stdbuf -oL hcitool -i " . $device . " lescan --duplicates 2>&1") || die('Unable to start scanning. Please make sure hcitool and stdbuf are installed!'); + while (<$hcitool>) { + chomp($_); + if ($_ eq 'LE Scan ...') { + syslogw(LOG_INFO, "Received '%s'.", $_); + } elsif (my ($fbmac, $fbname) = $_ =~ /^([\da-f]{2}:[\da-f]{2}:[\da-f]{2}:[\da-f]{2}:[\da-f]{2}:[\da-f]{2})\s(.*)$/i) { + #syslogw(LOG_DEBUG, "Received advertisement from bluetooth mac address '%s' with name '%s'.", $fbmac, $fbname); + update_device($fbmac, $fbname); + } elsif ( + $_ =~ m/^Set scan parameters failed: Input\/output error$/ || + $_ =~ m/^Invalid device: Network is down$/ + ) { + syslogw(LOG_WARNING, "Received '%s', resetting...", $_); + system(sprintf('hciconfig %s reset', $device)); + } else { + syslogw(LOG_WARNING, "Received unknown output: '%s'!", $_); + } + } + syslogw(LOG_WARNING, "hcitool exited, retrying..."); + close($hcitool); + sleep(RETRY_SLEEP); + } } sub handle_command($$) { - my ($buf, $current_client) = @_; - if (my ($mac, undef, $interval) = $buf =~ m/^\s*(([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2})\s*\|\s*(\d+)\s*$/) { - $mac = lc($mac); - if (my ($client) = grep { $current_client == $_->{'handle'} } @clients) { - syslogw(LOG_INFO, "Received query update for mac address %s, interval: %i by client %s:%i.", $mac, $interval, $current_client->peerhost(), $current_client->peerport()); - $client->{'mac'} = $mac; - $client->{'interval'} = $interval; - $client->{'next_check'} = 0; #now - } else { - syslogw(LOG_INFO, "Received query for mac address %s, interval: %i. Adding client %s:%i to clients list.", $mac, $interval, $current_client->peerhost(), $current_client->peerport()); - my %new_client; - $new_client{'handle'} = $current_client; - $new_client{'mac'} = $mac; - $new_client{'interval'} = $interval; - $new_client{'next_check'} = 0; #now - push(@clients, \%new_client); - } - print $current_client "command accepted\n" - } elsif ($buf =~ m/^\s*now\s*$/) { - syslogw(LOG_DEBUG, "Received now command from client %s:%i. Scheduling update...", $current_client->peerhost(), $current_client->peerport()); - foreach my $client (grep { $_->{'handle'} == $current_client } @clients) { - $client->{'next_check'} = 0; #now - } - print $current_client "command accepted\n" - } elsif ($buf =~ m/^\s*stop\s*$/) { - # Stop does not make sense when scanning permanently - syslogw(LOG_DEBUG, "Received stop command from client %s:%i. Pretending to care and ignoring...", $current_client->peerhost(), $current_client->peerport()); - print $current_client "no command running\n" # ToDo: Does the FHEM module even care? - } else { - syslogw(LOG_WARNING, "Received unknown command: '%s'.", $buf); - } + my ($buf, $current_client) = @_; + if (my ($mac, undef, $interval) = $buf =~ m/^\s*(([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2})\s*\|\s*(\d+)\s*$/) { + $mac = lc($mac); + if (my ($client) = grep { $current_client == $_->{'handle'} } @clients) { + syslogw(LOG_INFO, "Received query update for mac address %s, interval: %i by client %s:%i.", $mac, $interval, $current_client->peerhost(), $current_client->peerport()); + $client->{'mac'} = $mac; + $client->{'interval'} = $interval; + $client->{'next_check'} = 0; #now + } else { + syslogw(LOG_INFO, "Received query for mac address %s, interval: %i. Adding client %s:%i to clients list.", $mac, $interval, $current_client->peerhost(), $current_client->peerport()); + my %new_client; + $new_client{'handle'} = $current_client; + $new_client{'mac'} = $mac; + $new_client{'interval'} = $interval; + $new_client{'next_check'} = 0; #now + push(@clients, \%new_client); + } + print $current_client "command accepted\n" + } elsif ($buf =~ m/^\s*now\s*$/) { + syslogw(LOG_DEBUG, "Received now command from client %s:%i. Scheduling update...", $current_client->peerhost(), $current_client->peerport()); + foreach my $client (grep { $_->{'handle'} == $current_client } @clients) { + $client->{'next_check'} = 0; #now + } + print $current_client "command accepted\n" + } elsif ($buf =~ m/^\s*stop\s*$/) { + # Stop does not make sense when scanning permanently + syslogw(LOG_DEBUG, "Received stop command from client %s:%i. Pretending to care and ignoring...", $current_client->peerhost(), $current_client->peerport()); + print $current_client "no command running\n" # ToDo: Does the FHEM module even care? + } else { + syslogw(LOG_WARNING, "Received unknown command: '%s'.", $buf); + } } sub stats_task() { - my ($min_age, $max_age, $devices); - { - lock(%devices); - $devices = scalar(keys(%devices)); - foreach my $mac (keys(%devices)) { - my $age = time() - $devices{$mac}{'timestamp'}; - $min_age = $age if (!defined($min_age) || $age < $min_age); - $max_age = $age if (!defined($max_age) || $age > $max_age); - } - } - syslogw(LOG_INFO, "Active clients: %i, known devices: %i (min/max age: %s/%s)", scalar(@clients), $devices, $min_age // '%', $max_age // '%'); + my ($min_age, $max_age, $devices); + { + lock(%devices); + $devices = scalar(keys(%devices)); + foreach my $mac (keys(%devices)) { + my $age = time() - $devices{$mac}{'timestamp'}; + $min_age = $age if (!defined($min_age) || $age < $min_age); + $max_age = $age if (!defined($max_age) || $age > $max_age); + } + } + syslogw(LOG_INFO, "Active clients: %i, known devices: %i (min/max age: %s/%s)", scalar(@clients), $devices, $min_age // '%', $max_age // '%'); } sub cleanup_task() { - my $start_time = time(); - my $deleted_items = 0; - { - lock(%devices); - foreach my $mac (keys(%devices)) { - my $age = time() - $devices{$mac}{'timestamp'}; - if ($age > CLEANUP_MAX_AGE) { - $deleted_items++; - syslogw(LOG_DEBUG, "Deleting device %s.", $mac); - delete($devices{$mac}); - } - } - } - syslogw(LOG_INFO, "Cleanup finished, deleted %i devices in %i seconds.", $deleted_items, time() - $start_time); + my $start_time = time(); + my $deleted_items = 0; + { + lock(%devices); + foreach my $mac (keys(%devices)) { + my $age = time() - $devices{$mac}{'timestamp'}; + if ($age > CLEANUP_MAX_AGE) { + $deleted_items++; + syslogw(LOG_DEBUG, "Deleting device %s.", $mac); + delete($devices{$mac}); + } + } + } + syslogw(LOG_INFO, "Cleanup finished, deleted %i devices in %i seconds.", $deleted_items, time() - $start_time); } openlog(ME, 'pid', LOG_USER); -(my $device, my $daemonize, my $listen_address, my $listen_port, $syslog_level, my $legacy_mode, my $rssi_threshold) = parse_options(); +(my $device, my $daemonize, my $listen_address, my $listen_port, $syslog_level) = parse_options(); - local $SIG{INT} = local $SIG{TERM} = local $SIG{HUP} = sub { - syslogw(LOG_NOTICE, "Caught signal, cleaning up and exiting..."); - unlink(PIDFILE) if (-e PIDFILE); - closelog(); - exit(1); - }; + local $SIG{INT} = local $SIG{TERM} = local $SIG{HUP} = sub { + syslogw(LOG_NOTICE, "Caught signal, cleaning up and exiting..."); + unlink(PIDFILE) if (-e PIDFILE); + closelog(); + exit(1); + }; -syslogw(LOG_NOTICE, "Version %s started (device: %s, listen addr: %s, listen port: %s, daemonize: %i, legacy mode: %i, rssi threshold: %i, log level: %i).", - VERSION, $device, $listen_address, $listen_port, $daemonize, $legacy_mode, $rssi_threshold, $syslog_level); +syslogw(LOG_NOTICE, "Version %s started (device: %s, listen addr: %s, listen port: %s, daemonize: %i, log level: %i).", + VERSION, $device, $listen_address, $listen_port, $daemonize, $syslog_level); daemonize('root', 'root', PIDFILE) if $daemonize; -my $bluetooth_scan_thread = threads->new(\&bluetooth_scan_thread, $device, $legacy_mode)->detach(); -my $bluetooth_dump_thread = threads->new(\&bluetooth_dump_thread, $device)->detach() if (!$legacy_mode); +my $bluetooth_thread = threads->new(\&bluetooth_thread, $device)->detach(); my $current_client; $| = 1; my $server_socket = new IO::Socket::INET ( - LocalHost => $listen_address, - LocalPort => $listen_port, - Proto => 'tcp', - Listen => 5, - ReuseAddr => 1, + LocalHost => $listen_address, + LocalPort => $listen_port, + Proto => 'tcp', + Listen => 5, + ReuseAddr => 1, ); $server_socket or error_exit(1, "ERROR: Unable to create TCP server: $!, Exiting."); my $select = IO::Select->new($server_socket) or error_exit(1, "ERROR: Unable to select: $!, Exiting."); -my $next_stats_time = time() + STATS_INTERVAL; -my $next_cleanup_time = time() + CLEANUP_INTERVAL; - -$SIG{PIPE} = sub { - syslogw(LOG_INFO, "SIGPIPE received!"); -}; +my $next_stats_time = 0; +my $next_cleanup_time = 0; for(;;) { - # Process INET socket - foreach my $current_client ($select->can_read(0)) { - if($current_client == $server_socket) { - my $client_socket = $server_socket->accept(); - $select->add($client_socket); - syslogw(LOG_INFO, "Connection from %s:%s. Connected clients: %i.", $client_socket->peerhost(), $client_socket->peerport(), $select->count()-1); - } else { - sysread ($current_client, my $buf, INET_RECV_BUFFER); - if ($buf) { - chomp($buf); - handle_command($buf, $current_client); - } else { - $select->remove($current_client); - @clients = grep {$_->{'handle'} != $current_client} @clients; - syslogw(LOG_INFO, "Client %s:%s disconnected. Connected clients: %i.", $current_client->peerhost(), $current_client->peerport(), $select->count()-1); - $current_client->close(); - } - } - } + # Process INET socket + foreach my $current_client ($select->can_read(0)) { + if($current_client == $server_socket) { + my $client_socket = $server_socket->accept(); + $select->add($client_socket); + syslogw(LOG_INFO, "Connection from %s:%s. Connected clients: %i.", $client_socket->peerhost(), $client_socket->peerport(), $select->count()-1); + } else { + sysread ($current_client, my $buf, INET_RECV_BUFFER); + if ($buf) { + chomp($buf); + handle_command($buf, $current_client); + } else { + $select->remove($current_client); + @clients = grep {$_->{'handle'} != $current_client} @clients; + syslogw(LOG_INFO, "Client %s:%s disconnected. Connected clients: %i.", $current_client->peerhost(), $current_client->peerport(), $select->count()-1); + $current_client->close(); + } + } + } - # Check for updates due to a changed rssi - if (!$legacy_mode) { - lock(%devices); - my $devices = scalar(keys(%devices)); - foreach my $mac (keys(%devices)) { - if (abs($devices{$mac}{'reported_rssi'} - $devices{$mac}{'rssi'}) > $rssi_threshold) { - if (my @due_clients = grep { $_->{'mac'} eq $mac } @clients) { - syslogw(LOG_DEBUG, "Mac address %s needs update due to changed rssi. Old/new rssi: %i/%i, difference: %i, affected clients: %i.", $mac, $devices{$mac}{'reported_rssi'}, $devices{$mac}{'rssi'}, abs($devices{$mac}{'reported_rssi'} - $devices{$mac}{'rssi'}), scalar(@due_clients)); - foreach my $client (@due_clients) { - $client->{'next_check'} = 0; #now - } - } - } - } - } - - # Check for due client updates, cleanup, stats - # For performance reasons, a maximum of one task is performed per loop - if (my @due_clients = grep { time() >= $_->{'next_check'} } @clients) { - foreach my $client (@due_clients) { - if ( - defined($devices{$client->{'mac'}}) && - time()-$devices{$client->{'mac'}}{timestamp} <= $client->{'interval'} && - defined($devices{$client->{'mac'}}{prevtimestamp}) && time()-$devices{$client->{'mac'}}{prevtimestamp} <= $client->{'interval'} - ) { - syslogw(LOG_DEBUG, "Sending update for mac address %s, ages: %i/%i, max age: %i, rssi: %i, result: present.", $client->{'mac'}, time()-$devices{$client->{'mac'}}{'timestamp'}, time()-$devices{$client->{'mac'}}{'prevtimestamp'}, $client->{'interval'}, $devices{$client->{'mac'}}{'rssi'}); - printf {$client->{'handle'}} "present;device_name=%s;rssi=%s;daemon=%s V%s\n", $devices{$client->{'mac'}}{name}, $devices{$client->{'mac'}}{'rssi'}, ME, VERSION; - } else { - syslogw(LOG_DEBUG, "Sending update for mac address %s, max age: %i, result: absence.", $client->{'mac'}, $client->{'interval'}); - printf {$client->{'handle'}} "absence;rssi=unreachable;daemon=%s V%s\n", ME, VERSION; - } - if (defined($devices{$client->{'mac'}})) { - lock(%devices); - $devices{$client->{'mac'}}{'reported_rssi'} = $devices{$client->{'mac'}}{'rssi'}; - } - $client->{'next_check'} = time() + $client->{'interval'}; - } - } elsif (time() > $next_cleanup_time) { - cleanup_task(); - $next_cleanup_time = time() + CLEANUP_INTERVAL; - } elsif (time() > $next_stats_time) { - stats_task(); - $next_stats_time = time() + STATS_INTERVAL; - } + # Check for due client updates, cleanup, stats + # For performance reasons, a maximum of one task is performed per loop + if (my @due_clients = grep { time() >= $_->{'next_check'} } @clients) { + foreach my $client (@due_clients) { + if ( + defined($devices{$client->{'mac'}}) && + time()-$devices{$client->{'mac'}}{timestamp} <= $client->{'interval'} + ) { + syslogw(LOG_DEBUG, "Sending update for mac address %s, age: %i, max age: %i, result: present.", $client->{'mac'}, time()-$devices{$client->{'mac'}}{timestamp}, $client->{'interval'}); + printf {$client->{'handle'}} "present;%s\n", $devices{$client->{'mac'}}{name} + } else { + syslogw(LOG_DEBUG, "Sending update for mac address %s, max age: %i, result: absence.", $client->{'mac'}, $client->{'interval'}); + print {$client->{'handle'}} "absence\n" + } + $client->{'next_check'} = time() + $client->{'interval'}; + } + } elsif (time() > $next_cleanup_time) { + cleanup_task(); + $next_cleanup_time = time() + CLEANUP_INTERVAL; + } elsif (time() > $next_stats_time) { + stats_task(); + $next_stats_time = time() + STATS_INTERVAL; + } - usleep(MAINLOOP_SLEEP_US); + usleep(MAINLOOP_SLEEP_US); } $server_socket->close();