mirror of https://github.com/aredn/aredn.git
389 lines
11 KiB
Perl
Executable File
389 lines
11 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
=for comment
|
|
Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks
|
|
Copyright (C) 2015 Joe Ayers ae6xe@arrl.net
|
|
See Contributors file for additional contributors
|
|
|
|
This program is 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 version 3 of the License.
|
|
|
|
This program 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.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
Additional Terms:
|
|
|
|
Additional use restrictions exist on the AREDN(TM) trademark and logo.
|
|
See AREDNLicense.txt for more info.
|
|
|
|
Attributions to the AREDN Project must be retained in the source code.
|
|
If importing this code into a new or existing project attribution
|
|
to the AREDN project must be added to the source code.
|
|
|
|
You must not misrepresent the origin of the material contained within.
|
|
|
|
Modified versions must be modified to attribute to the original source
|
|
and be marked in reasonable ways as differentiate it from the original
|
|
version.
|
|
|
|
=cut
|
|
|
|
# Joe Ayers AE6XE ae6xe@arrl.net 2015-12-08
|
|
# work around Atheros ANI overly attenuating recieve chain with tendency to become stuck
|
|
# A receive chain may go deaf at noise prone sites and some neighbors may drop out.
|
|
# The wireless driver poorly tunes and treats these neighbors as noise in error.
|
|
# This is a workaround until root cause driver updates occur.
|
|
|
|
chomp ($iface=`uci -q get 'network.wifi.ifname'`); # wireless interface
|
|
$wifi_disable = ( $iface =~ /eth.*$/ )? 1 : 0;
|
|
exit 0 if $wifi_disable;
|
|
|
|
|
|
$now=`cat /proc/uptime | cut -f1 -d" "`;
|
|
chomp $now;
|
|
exit 0 unless $now > 119;
|
|
|
|
sleep 3; # wait for snrlog to see that we are not running
|
|
|
|
$rssipid="/tmp/rssi_monitor.pid";
|
|
if ( -f "$rssipid" )
|
|
{
|
|
chomp (${rssipidvalue}=`cat $rssipid`);
|
|
exit 0 if ( ${rssipidvalue} > 0 and -d "/proc/${rssipidvalue}" );
|
|
}
|
|
|
|
open(my $mypid, '>', $rssipid) or die("Could not open $rssipid. $!");
|
|
print $mypid $$;
|
|
close $mypid;
|
|
|
|
$snrlogpid="/tmp/snrlog.pid";
|
|
if ( -f "$snrlogpid" )
|
|
{
|
|
chomp (${snrlogpidvalue}=`cat $snrlogpid`);
|
|
$waitcount=0;
|
|
while ( ${snrlogpidvalue} > 0 and -d "/proc/${snrlogpidvalue}" and $waitcount < 4)
|
|
{
|
|
sleep 5;
|
|
$waitcount+=1;
|
|
}
|
|
if ( $waitcount == 4 ) # skip this turn if snrlog still running
|
|
{
|
|
unlink $rssipid;
|
|
exit 0;
|
|
}
|
|
}
|
|
|
|
sleep 7;
|
|
|
|
foreach(`iwinfo $iface info`)
|
|
{
|
|
next unless /^.*PHY name:\s*([a-z0-4]+)/;
|
|
$phy = $1;
|
|
}
|
|
$phy = "phy0" unless $phy;
|
|
|
|
$datfile = "/tmp/rssi.dat";
|
|
$logfile = "/tmp/rssi.log";
|
|
|
|
open(my $lfh, '>>', $logfile) or die "Could not open file $logfile $!";
|
|
|
|
sub getRSSI
|
|
{
|
|
for (keys %rssi)
|
|
{
|
|
delete $rssi{$_};
|
|
}
|
|
|
|
chomp ($stationCount = `ls -1 /sys/kernel/debug/ieee80211/${phy}/netdev:${iface}/stations | wc -l`);
|
|
|
|
if ($stationCount >= 1)
|
|
{
|
|
open(FILE, "/usr/sbin/iw $iface station dump 2>&1 |") or die "/usr/sbin/iw failed $!";
|
|
|
|
$neighborCount = 0;
|
|
while($line = <FILE>)
|
|
{
|
|
if($line =~ /Station (\S+) \(on $iface\)/) { $mac = $1;}
|
|
if($antnum and $line =~ /signal:[ \t]+[-\d]+[ \t]*\[([-\d]+),[ \t]*([-\d]+)/)
|
|
{
|
|
$H = $1;
|
|
$V = $2;
|
|
}
|
|
|
|
if ((not $antnum) and $line =~ /signal:[ \t]+[-\d]+[ \t]*\[([-\d]+)\]/)
|
|
{
|
|
$H = $1;
|
|
}
|
|
if ($H)
|
|
{
|
|
if ($H < -95) { $rssi{$mac}{"Hrssi"}=-96 ; }
|
|
else { $rssi{$mac}{"Hrssi"}=$H ; }
|
|
undef $H;
|
|
$neighborCount += 1;
|
|
}
|
|
if ($V)
|
|
{
|
|
if ($V < -95) { $rssi{$mac}{"Vrssi"}=-96 ; }
|
|
else { $rssi{$mac}{"Vrssi"}=$V ; }
|
|
undef $V;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sub getChannelScan
|
|
{
|
|
$iface =~ /wlan(\d+)/;
|
|
$radio = "radio$1";
|
|
$chnum = `uci get wireless.$radio.channel`;
|
|
$chnum += 1;
|
|
if ($chnum == 8 or $chnum == 12 or $chnum == 100 or $chnum == 185) { $chnum -= 2; }
|
|
if ($chnum == 0) { $chnum = 1; }
|
|
$freq = `iwinfo $iface freqlist | grep "Channel $chnum" | head -1 | sed -e "s/\\.//"`;
|
|
$freq =~ /([\d]+)[ \t]+GHz/;
|
|
$freq = $1;
|
|
}
|
|
|
|
$antnum=`cat /sys/kernel/debug/ieee80211/${phy}/ath9k/tx_chainmask`;
|
|
if ($antnum == "1")
|
|
{
|
|
$antnum=0;
|
|
}
|
|
else
|
|
{
|
|
$antnum=1; # more than one
|
|
}
|
|
|
|
if ( -e $datfile )
|
|
{
|
|
open(FILE, "<$datfile") or die "Unable to read \"$datfile\"";
|
|
|
|
while($line = <FILE>)
|
|
{
|
|
if ($antnum)
|
|
{
|
|
($mac, $aveH, $aveV, $stdDevH, $stdDevV, $numS, $last) = split /\|/, $line;
|
|
$rssiHist{$mac}{"aveV"} = $aveV;
|
|
$rssiHist{$mac}{"sdV"} = $stdDevV;
|
|
}
|
|
else
|
|
{
|
|
($mac, $aveH, $stdDevH, $numS, $last) = split /\|/, $line;
|
|
}
|
|
$rssiHist{$mac}{"aveH"} = $aveH;
|
|
$rssiHist{$mac}{"sdH"} = $stdDevH;
|
|
$rssiHist{$mac}{"num"} = $numS;
|
|
chomp $last;
|
|
$rssiHist{$mac}{"last"} = $last;
|
|
}
|
|
close FILE ;
|
|
}
|
|
|
|
$ofdm_level = `cat /sys/kernel/debug/ieee80211/${phy}/ath9k/ani | grep "OFDM LEVEL" | cut -f2 -d: `;
|
|
$now=`cat /proc/uptime | cut -f1 -d" "`;
|
|
chomp $now;
|
|
|
|
if ($now < 750 and $now > 119 )
|
|
{
|
|
# avoid node going deaf while trying to obtain 'normal' statistics of neighbor strength
|
|
# in first few minutes after boot
|
|
getChannelScan();
|
|
system("/usr/sbin/iw $iface scan freq $freq passive > /dev/null");
|
|
}
|
|
|
|
getRSSI() ;
|
|
|
|
for (keys %rssi)
|
|
{
|
|
if ( $rssiHist{$_} and $now - $rssiHist{$_}{"last"} < 3600 )
|
|
{
|
|
$hit = 0 ;
|
|
$sdH3 = int(3 * $rssiHist{$_}{"sdH"} + .5);
|
|
# is the RSSI attenuated and 3 standard deviations away?
|
|
if (abs ($rssiHist{$_}{"aveH"} - $rssi{$_}{"Hrssi"}) > $sdH3) { $hit += 1; }
|
|
if ( $antnum )
|
|
{
|
|
$sdV3 = int(3 * $rssiHist{$_}{"sdV"} + .5);
|
|
if (abs ( $rssiHist{$_}{"aveV"} - $rssi{$_}{"Vrssi"}) > $sdV3) { $hit += 1; }
|
|
}
|
|
|
|
if ($rssiHist{$_}{"num"} > 9 and $ofdm_level <= 3 and $hit > 0)
|
|
{
|
|
# Overly Attenuated Chain Suspected
|
|
$datestring = localtime();
|
|
if ($antnum)
|
|
{
|
|
print $lfh "$datestring: Attenuated Suspect $_ [$rssi{$_}{'Hrssi'},$rssi{$_}{'Vrssi'}] $rssiHist{$_}{'aveH'} ";
|
|
print $lfh "$rssiHist{$_}{'aveV'} $rssiHist{$_}{'sdH'} $rssiHist{$_}{'sdV'}\n";
|
|
}
|
|
else
|
|
{
|
|
print $lfh "$datestring: Attenuated Suspect $_ [$rssi{$_}{'Hrssi'}] $rssiHist{$_}{'aveH'} $rssiHist{$_}{'sdH'}\n";
|
|
}
|
|
|
|
# find strongest signal to compare RSSI before/after reset
|
|
if ( $amac )
|
|
{
|
|
if ($antnum)
|
|
{
|
|
if ($rssi{$amac}{"Hrssi"} < $rssi{$amac}{"Vrssi"} ) { $strong1 = "Vrssi" ;} else {$strong1 = "Hrssi"; }
|
|
if ($rssi{$_}{"Hrssi"} < $rssi{$_}{"Vrssi"} ) { $strong2 = "Vrssi" ;} else {$strong2 = "Hrssi"; }
|
|
if ($rssi{$amac}{$strong1} < $rssi{$_}{$strong2} ) { $amac = $_ ;}
|
|
}
|
|
else
|
|
{
|
|
if ($rssi{$amac}{"Hrssi"} < $rssi{$_}{"Hrssi"} ) { $amac = $_ ;}
|
|
}
|
|
}
|
|
else { $amac = $_ ; }
|
|
next ; # do not update statistics when suspected condition
|
|
}
|
|
|
|
# unpdate statistics
|
|
$aveH = (($rssiHist{$_}{"aveH"}*$rssiHist{$_}{"num"})+$rssi{$_}{"Hrssi"}) / ($rssiHist{$_}{"num"} + 1 );
|
|
$sdH = sqrt((($rssiHist{$_}{"num"}-1)*($rssiHist{$_}{"sdH"}**2) +
|
|
(($rssi{$_}{"Hrssi"}-$aveH)*($rssi{$_}{"Hrssi"}-$rssiHist{$_}{"aveH"})))/$rssiHist{$_}{"num"});
|
|
chomp $aveH;
|
|
chomp $sdH;
|
|
|
|
if ($antnum)
|
|
{
|
|
$aveV = (($rssiHist{$_}{"aveV"}*$rssiHist{$_}{"num"})+$rssi{$_}{"Vrssi"}) / ($rssiHist{$_}{"num"} + 1 );
|
|
$sdV = sqrt((($rssiHist{$_}{"num"}-1)*($rssiHist{$_}{"sdV"}**2) +
|
|
(($rssi{$_}{"Vrssi"}-$aveV)*($rssi{$_}{"Vrssi"}-$rssiHist{$_}{"aveV"})))/$rssiHist{$_}{"num"});
|
|
chomp $aveV;
|
|
chomp $sdV;
|
|
}
|
|
|
|
$rssiHist{$_}{"aveH"} = $aveH;
|
|
$rssiHist{$_}{"sdH"} = $sdH;
|
|
$rssiHist{$_}{"last"} = $now;
|
|
if ($rssiHist{$_}{"num"} < 60 )
|
|
{
|
|
# keep statistics to 60 sample (minute) moving window
|
|
$rssiHist{$_}{"num"} += 1;
|
|
}
|
|
if ($antnum)
|
|
{
|
|
$rssiHist{$_}{"aveV"} = $aveV;
|
|
$rssiHist{$_}{"sdV"} = $sdV;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
# new neigbor or data too old--restart history
|
|
$rssiHist{$_}{"aveH"} = $rssi{$_}{"Hrssi"};
|
|
$rssiHist{$_}{"sdH"} = 0;
|
|
$rssiHist{$_}{"num"} = 1;
|
|
$rssiHist{$_}{"last"} = $now;
|
|
if ($antnum)
|
|
{
|
|
$rssiHist{$_}{"aveV"} = $rssi{$_}{"Vrssi"};
|
|
$rssiHist{$_}{"sdV"} = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($amac)
|
|
{
|
|
getChannelScan();
|
|
|
|
$datestring = localtime();
|
|
if ($antnum) {print $lfh "$datestring: before $amac [ $rssi{$amac}{'Hrssi'}, $rssi{$amac}{'Vrssi'} ]\n";}
|
|
else {print $lfh "$datestring: before $amac [ $rssi{$amac}{'Hrssi'}]\n";}
|
|
|
|
system("/usr/sbin/iw $iface scan freq $freq passive > /dev/null");
|
|
|
|
sleep 5;
|
|
|
|
$beforeH = $rssi{$amac}{"Hrssi"};
|
|
if ($antnum) { $beforeV = $rssi{$amac}{"Vrssi"}; }
|
|
|
|
getRSSI() ;
|
|
$datestring = localtime();
|
|
if ($antnum) {print $lfh "$datestring: after $amac [ $rssi{$amac}{'Hrssi'}, $rssi{$amac}{'Vrssi'} ]\n";}
|
|
else {print $lfh "$datestring: after $amac [ $rssi{$amac}{'Hrssi'}]\n";}
|
|
|
|
$falpos = 0;
|
|
if ($antnum)
|
|
{
|
|
if (abs ( $beforeH - $rssi{$amac}{"Hrssi"} ) <= 2 and
|
|
abs ( $beforeV - $rssi{$amac}{"Vrssi"} ) <= 2 ) { $falpos = 1; }
|
|
}
|
|
elsif (abs ( $beforeH - $rssi{$amac}{"Hrssi"} ) <= 2 ) { $falpos = 1; }
|
|
|
|
if ( $falpos )
|
|
{
|
|
# if a false-positive (within 2dB change after a reset), then add data point to statistics
|
|
$aveH = (($rssiHist{$amac}{"aveH"}*$rssiHist{$amac}{"num"})+ $beforeH )
|
|
/ ($rssiHist{$amac}{"num"} + 1 );
|
|
$sdH = sqrt((($rssiHist{$amac}{"num"}-1)*($rssiHist{$amac}{"sdH"}**2) +
|
|
(($beforeH-$aveH)*($beforeH-$rssiHist{$amac}{"aveH"})))
|
|
/$rssiHist{$amac}{"num"});
|
|
chomp $aveH;
|
|
chomp $sdH;
|
|
$rssiHist{$amac}{"aveH"} = $aveH;
|
|
$rssiHist{$amac}{"sdH"} = $sdH;
|
|
|
|
if ($antnum)
|
|
{
|
|
$aveV = (($rssiHist{$amac}{"aveV"}*$rssiHist{$amac}{"num"})+ $beforeV ) /
|
|
($rssiHist{$amac}{"num"} + 1 );
|
|
$sdV = sqrt((($rssiHist{$amac}{"num"}-1)*($rssiHist{$amac}{"sdV"}**2) +
|
|
(($beforeV-$aveV)*($beforeV-$rssiHist{$amac}{"aveV"}))) /
|
|
$rssiHist{$amac}{"num"});
|
|
chomp $aveV;
|
|
chomp $sdV;
|
|
$rssiHist{$amac}{"aveV"} = $aveV;
|
|
$rssiHist{$amac}{"sdV"} = $sdV;
|
|
}
|
|
|
|
if ($rssiHist{$amac}{"num"} < 60 )
|
|
{
|
|
# keep statistics to 60 sample (minute) moving window
|
|
$rssiHist{$amac}{"num"} += 1;
|
|
}
|
|
$rssiHist{$amac}{"last"} = $now + 5 ;
|
|
|
|
$datestring = localtime();
|
|
print $lfh "$datestring: $amac Possible valid data point, adding to statistics.\n";
|
|
}
|
|
}
|
|
|
|
close $lfh;
|
|
|
|
open($dfh, ">$datfile") or die "Unable to create \"$datfile\" $!";
|
|
for (keys %rssiHist)
|
|
{
|
|
if ($antnum)
|
|
{
|
|
print $dfh "$_|$rssiHist{$_}{'aveH'}|$rssiHist{$_}{'aveV'}|$rssiHist{$_}{'sdH'}|";
|
|
print $dfh "$rssiHist{$_}{'sdV'}|$rssiHist{$_}{'num'}|$rssiHist{$_}{'last'}\n";
|
|
}
|
|
else
|
|
{
|
|
print $dfh "$_|$rssiHist{$_}{'aveH'}|$rssiHist{$_}{'sdH'}|$rssiHist{$_}{'num'}|$rssiHist{$_}{'last'}\n";
|
|
}
|
|
}
|
|
|
|
close $dfh;
|
|
unlink $rssipid;
|
|
|
|
# when logfile gets 1k over $MAXSIZE, then chop down
|
|
$MAXSIZE = 2**14;
|
|
exit 0 unless -s $logfile > $MAXSIZE + 1024;
|
|
@ARGV = $logfile;
|
|
undef $/;
|
|
$^I = "";
|
|
while (<>)
|
|
{
|
|
substr($_, 0, length() - $MAXSIZE) = "";
|
|
s/.*\n//;
|
|
print;
|
|
}
|