diff --git a/files/etc/crontabs/root b/files/etc/crontabs/root index 8d2a6c30..35e45dd2 100644 --- a/files/etc/crontabs/root +++ b/files/etc/crontabs/root @@ -1 +1,2 @@ */5 * * * * /usr/local/bin/fccid +*/1 * * * * /usr/local/bin/rssi_monitor diff --git a/files/usr/local/bin/rssi_monitor b/files/usr/local/bin/rssi_monitor new file mode 100755 index 00000000..c09d180c --- /dev/null +++ b/files/usr/local/bin/rssi_monitor @@ -0,0 +1,299 @@ +#!/usr/bin/perl +# +# GPL V2 or greater +# work around Atheros ANI overly attenuating recieve chain with tendency to become stuck +# Joe Ayers AE6XE ae6xe@arrl.net 2015-10-29 + +# 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. + +$now=`cat /proc/uptime | cut -f1 -d" "`; +chomp $now; + +exit 0 unless $now > 120; + +$iface = "wlan0" ; # wireless interface +$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{$_}; + } + + open(FILE, "/usr/sbin/iw $iface station dump 2>&1 |") or die "/usr/sbin/iw failed $!"; + + $neighborCount = 0; + while($line = ) + { + 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; + } + } +} + +$antnum=`iw list | grep "Configured Antennas: TX" | cut -f6 -d" "`; +chomp $antnum; +if ($antnum eq "0x1") +{ + $antnum=0; +} +else +{ + $antnum=1; # more than one +} + +if ( -e $datfile ) +{ + open(FILE, "<$datfile") or die "Unable to read \"$datfile\""; + + while($line = ) + { + 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/phy0/ath9k/ani | grep "OFDM LEVEL" | cut -f2 -d: `; +$now=`cat /proc/uptime | cut -f1 -d" "`; +chomp $now; + +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? Test is only 1 chain has dropped, not both. + if ($rssiHist{$_}{"aveH"} - $rssi{$_}{"Hrssi"} > $sdH3) { $hit += 1; } + if ( $antnum ) + { + $sdV3 = int(3 * $rssiHist{$_}{"sdV"} + .5); + if ($rssiHist{$_}{"aveV"} - $rssi{$_}{"Vrssi"} > $sdV3) { $hit += 1; } + } + + if ($rssiHist{$_}{"num"} > 9 and $ofdm_level <= 2 and $hit == 1) + { # 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 or not $neighborCount) +{ + $chnum = `uci get wireless.radio0.channel`; + $chnum += 1; + if ($chnum == 8 or $chnum == 12 or $chnum == 100 or $chnum == 185) { $chnum -= 2; } + if ($chnum == 0) { $chnum = 1; } + $freq = `iw list | grep "\\\[$chnum\\\]" | head -1`; + $freq =~ /([\d]+)[ \t]+MHz[ \t]+/; + $freq = $1; + + if ($amac) + { + $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"); + + if ($amac) + { + 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; + +# 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, $MAXSIZE - length) = ""; + s/.*\n//; + print; +}