mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-03-13 05:06:35 +00:00
lepresenced: Update to V0.8, added rssi, added V0.8 deb package
git-svn-id: https://svn.fhem.de/fhem/trunk@13581 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
539269461b
commit
cea779c61d
BIN
fhem/contrib/PRESENCE/deb/lepresenced-0.8-1.deb
Normal file
BIN
fhem/contrib/PRESENCE/deb/lepresenced-0.8-1.deb
Normal file
Binary file not shown.
@ -45,17 +45,28 @@ use Net::Server::Daemonize qw(daemonize);
|
|||||||
|
|
||||||
use constant RETRY_SLEEP => 1;
|
use constant RETRY_SLEEP => 1;
|
||||||
use constant INET_RECV_BUFFER => 1024;
|
use constant INET_RECV_BUFFER => 1024;
|
||||||
use constant MAINLOOP_SLEEP_US => 100 * 1000;
|
use constant MAINLOOP_SLEEP_US => 250 * 1000;
|
||||||
|
|
||||||
use constant CLEANUP_INTERVAL => 15 * 60;
|
use constant CLEANUP_INTERVAL => 15 * 60;
|
||||||
use constant CLEANUP_MAX_AGE => 30 * 60;
|
use constant CLEANUP_MAX_AGE => 30 * 60;
|
||||||
use constant STATS_INTERVAL => 5 * 60;
|
use constant STATS_INTERVAL => 5 * 60;
|
||||||
|
|
||||||
|
use constant DEFAULT_RSSI_THRESHOLD => 10;
|
||||||
|
use constant RSSI_WINDOW => 10;
|
||||||
|
|
||||||
use constant ME => 'lepresenced';
|
use constant ME => 'lepresenced';
|
||||||
use constant VERSION => '0.6';
|
use constant VERSION => '0.8';
|
||||||
|
|
||||||
use constant PIDFILE => '/var/run/' . ME . '.pid';
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
my %devices :shared;
|
my %devices :shared;
|
||||||
my @clients = ();
|
my @clients = ();
|
||||||
my $syslog_level;
|
my $syslog_level;
|
||||||
@ -86,6 +97,9 @@ sub usage_exit() {
|
|||||||
printf("\t%s -b <bluetooth device> -a <listen address> -p <listen port> -l <log level> -d\n", ME);
|
printf("\t%s -b <bluetooth device> -a <listen address> -p <listen port> -l <log level> -d\n", ME);
|
||||||
print("valid log levels:\n");
|
print("valid log levels:\n");
|
||||||
print("\tLOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG. Default: LOG_INFO\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");
|
print("examples:\n");
|
||||||
printf("\t%s --bluetoothdevice hci0 --listenaddress 127.0.0.1 --listenport 5333 --daemon\n", ME);
|
printf("\t%s --bluetoothdevice hci0 --listenaddress 127.0.0.1 --listenport 5333 --daemon\n", ME);
|
||||||
printf("\t%s --loglevel LOG_DEBUG --daemon\n", ME);
|
printf("\t%s --loglevel LOG_DEBUG --daemon\n", ME);
|
||||||
@ -99,23 +113,30 @@ sub parse_options() {
|
|||||||
my $listen_address = "0.0.0.0";
|
my $listen_address = "0.0.0.0";
|
||||||
my $listen_port = "5333";
|
my $listen_port = "5333";
|
||||||
my $syslog_level = "LOG_INFO";
|
my $syslog_level = "LOG_INFO";
|
||||||
|
my $legacy_mode = 0;
|
||||||
|
my $rssi_threshold = DEFAULT_RSSI_THRESHOLD;
|
||||||
|
|
||||||
GetOptions(
|
GetOptions(
|
||||||
'bluetoothdevice|device|b=s' => \$device,
|
'bluetoothdevice|device|b=s' => \$device,
|
||||||
'daemon|daemonize|d!' => \$daemonize,
|
'daemon|daemonize|d!' => \$daemonize,
|
||||||
'listenaddress|address|a=s' => \$listen_address,
|
'listenaddress|address|a=s' => \$listen_address,
|
||||||
'listenport|port|p=i' => \$listen_port,
|
'listenport|port|p=i' => \$listen_port,
|
||||||
'loglevel|l=s' => \$syslog_level
|
'loglevel|l=s' => \$syslog_level,
|
||||||
|
'legacymode|legacy!' => \$legacy_mode,
|
||||||
|
'rssithreshold=i' => \$rssi_threshold,
|
||||||
) or usage_exit();
|
) or usage_exit();
|
||||||
|
|
||||||
|
usage_exit() if ($rssi_threshold < 5);
|
||||||
|
|
||||||
$listen_address =~ m/^\d+\.\d+\.\d+\.\d+$/ 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 =~ m/^LOG_(EMERG|ALERT|CRIT|ERR|WARNING|NOTICE|INFO|DEBUG)$/ or usage_exit();
|
||||||
$syslog_level = eval($syslog_level);
|
$syslog_level = eval($syslog_level);
|
||||||
|
|
||||||
return ($device, $daemonize, $listen_address, $listen_port, $syslog_level);
|
return ($device, $daemonize, $listen_address, $listen_port, $syslog_level, $legacy_mode, $rssi_threshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub update_device($$) {
|
sub update_device($$$) {
|
||||||
my ($mac, $name) = @_;
|
my ($mac, $name, $rssi) = @_;
|
||||||
$mac = lc($mac);
|
$mac = lc($mac);
|
||||||
{
|
{
|
||||||
lock(%devices);
|
lock(%devices);
|
||||||
@ -123,13 +144,26 @@ sub update_device($$) {
|
|||||||
my %device :shared;
|
my %device :shared;
|
||||||
$devices{$mac} = \%device;
|
$devices{$mac} = \%device;
|
||||||
}
|
}
|
||||||
$devices{$mac}{'name'} = $name unless ($name eq '(unknown)' && defined($devices{$mac}{'name'}));
|
$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}{'timestamp'} = time();
|
$devices{$mac}{'timestamp'} = time();
|
||||||
}
|
}
|
||||||
|
#dump_devices();
|
||||||
}
|
}
|
||||||
|
|
||||||
sub bluetooth_thread($) {
|
sub dump_devices() {
|
||||||
my ($device) = @_;
|
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;
|
my $hcitool;
|
||||||
for(;;) {
|
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!');
|
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!');
|
||||||
@ -138,8 +172,10 @@ sub bluetooth_thread($) {
|
|||||||
if ($_ eq 'LE Scan ...') {
|
if ($_ eq 'LE Scan ...') {
|
||||||
syslogw(LOG_INFO, "Received '%s'.", $_);
|
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) {
|
} 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);
|
#syslogw(LOG_DEBUG, "Received advertisement from bluetooth mac address '%s' with name '%s'.", $fbmac, $fbname);
|
||||||
update_device($fbmac, $fbname);
|
update_device($fbmac, $fbname, 'unknown');
|
||||||
|
}
|
||||||
} elsif (
|
} elsif (
|
||||||
$_ =~ m/^Set scan parameters failed: Input\/output error$/ ||
|
$_ =~ m/^Set scan parameters failed: Input\/output error$/ ||
|
||||||
$_ =~ m/^Invalid device: Network is down$/
|
$_ =~ m/^Invalid device: Network is down$/
|
||||||
@ -156,6 +192,79 @@ sub bluetooth_thread($) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 handle_command($$) {
|
sub handle_command($$) {
|
||||||
my ($buf, $current_client) = @_;
|
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*$/) {
|
if (my ($mac, undef, $interval) = $buf =~ m/^\s*(([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2})\s*\|\s*(\d+)\s*$/) {
|
||||||
@ -222,7 +331,7 @@ sub cleanup_task() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
openlog(ME, 'pid', LOG_USER);
|
openlog(ME, 'pid', LOG_USER);
|
||||||
(my $device, my $daemonize, my $listen_address, my $listen_port, $syslog_level) = parse_options();
|
(my $device, my $daemonize, my $listen_address, my $listen_port, $syslog_level, my $legacy_mode, my $rssi_threshold) = parse_options();
|
||||||
|
|
||||||
local $SIG{INT} = local $SIG{TERM} = local $SIG{HUP} = sub {
|
local $SIG{INT} = local $SIG{TERM} = local $SIG{HUP} = sub {
|
||||||
syslogw(LOG_NOTICE, "Caught signal, cleaning up and exiting...");
|
syslogw(LOG_NOTICE, "Caught signal, cleaning up and exiting...");
|
||||||
@ -231,12 +340,13 @@ openlog(ME, 'pid', LOG_USER);
|
|||||||
exit(1);
|
exit(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
syslogw(LOG_NOTICE, "Version %s started (device: %s, listen addr: %s, listen port: %s, daemonize: %i, log level: %i).",
|
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, $syslog_level);
|
VERSION, $device, $listen_address, $listen_port, $daemonize, $legacy_mode, $rssi_threshold, $syslog_level);
|
||||||
|
|
||||||
daemonize('root', 'root', PIDFILE) if $daemonize;
|
daemonize('root', 'root', PIDFILE) if $daemonize;
|
||||||
|
|
||||||
my $bluetooth_thread = threads->new(\&bluetooth_thread, $device)->detach();
|
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 $current_client;
|
my $current_client;
|
||||||
$| = 1;
|
$| = 1;
|
||||||
@ -250,8 +360,12 @@ my $server_socket = new IO::Socket::INET (
|
|||||||
$server_socket or error_exit(1, "ERROR: Unable to create TCP server: $!, Exiting.");
|
$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 $select = IO::Select->new($server_socket) or error_exit(1, "ERROR: Unable to select: $!, Exiting.");
|
||||||
|
|
||||||
my $next_stats_time = 0;
|
my $next_stats_time = time() + STATS_INTERVAL;
|
||||||
my $next_cleanup_time = 0;
|
my $next_cleanup_time = time() + CLEANUP_INTERVAL;
|
||||||
|
|
||||||
|
$SIG{PIPE} = sub {
|
||||||
|
syslogw(LOG_INFO, "SIGPIPE received!");
|
||||||
|
};
|
||||||
|
|
||||||
for(;;) {
|
for(;;) {
|
||||||
# Process INET socket
|
# Process INET socket
|
||||||
@ -274,19 +388,36 @@ for(;;) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 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
|
# Check for due client updates, cleanup, stats
|
||||||
# For performance reasons, a maximum of one task is performed per loop
|
# For performance reasons, a maximum of one task is performed per loop
|
||||||
if (my @due_clients = grep { time() >= $_->{'next_check'} } @clients) {
|
if (my @due_clients = grep { time() >= $_->{'next_check'} } @clients) {
|
||||||
foreach my $client (@due_clients) {
|
foreach my $client (@due_clients) {
|
||||||
if (
|
if (defined($devices{$client->{'mac'}}) && time()-$devices{$client->{'mac'}}{timestamp} <= $client->{'interval'}) {
|
||||||
defined($devices{$client->{'mac'}}) &&
|
syslogw(LOG_DEBUG, "Sending update for mac address %s, age: %i, max age: %i, rssi: %i, result: present.", $client->{'mac'}, time()-$devices{$client->{'mac'}}{'timestamp'}, $client->{'interval'}, $devices{$client->{'mac'}}{'rssi'});
|
||||||
time()-$devices{$client->{'mac'}}{timestamp} <= $client->{'interval'}
|
printf {$client->{'handle'}} "present;device_name=%s;rssi=%s;daemon=%s V%s\n", $devices{$client->{'mac'}}{name}, $devices{$client->{'mac'}}{'rssi'}, ME, VERSION;
|
||||||
) {
|
|
||||||
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 {
|
} else {
|
||||||
syslogw(LOG_DEBUG, "Sending update for mac address %s, max age: %i, result: absence.", $client->{'mac'}, $client->{'interval'});
|
syslogw(LOG_DEBUG, "Sending update for mac address %s, max age: %i, result: absence.", $client->{'mac'}, $client->{'interval'});
|
||||||
print {$client->{'handle'}} "absence\n"
|
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'};
|
$client->{'next_check'} = time() + $client->{'interval'};
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user