aredn/files/usr/local/bin/snrlog

424 lines
10 KiB
Plaintext
Raw Normal View History

#!/usr/bin/lua
--[[
Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks
Copyright (C) 2016 Darryl Quinn
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
--]]
require("aredn.utils")
require("aredn.info")
require("uci")
require("aredn.uci")
local nxo=require("nixio")
require("iwinfo")
require("luci.sys")
-- setup extensions
os.capture=capture
string.print=print_r
-- delay just after rssi_monitor has a chance to run noise floor calibration
local MAXLINES=2880 -- 2 days worth
local AGETIME=43200
local INACTIVETIMEOUT=10000
local tmpdir="/tmp/snrlog"
local lastdat="/tmp/snr.dat"
local lasttime={}
local lines={}
local arpcache={}
local nscache={}
local defnoise=-95
local tmpdirlist={}
local neighbors={}
local stations={}
local wifiiface=""
local bandwidth=""
local nulledout={}
local pidfile="/tmp/snrlog.pid"
local rssifile="/tmp/rssi_monitor.pid"
-- Neighbor Class
Neighbor={}
Neighbor.__index=Neighbor
function Neighbor.create(macaddress)
2018-07-19 19:19:29 -06:00
local n={}
setmetatable(n,Neighbor)
n.mac=macaddress:lower()
n.hostname=nil
n.sigfile=nil
n.datafile=nil
n.ip=nil
n.lastseen=nil
n.level=nil
n.ip=n:findIp()
n.hostname=n:findHostname()
n.signal=n:findSignal()
n.noise=n:findNoise()
n.tx_mcs=n:findTxMcs()
n.tx_rate=n:findTxRate()
n.rx_mcs=n:findRxMcs()
n.rx_rate=n:findRxRate()
n.lastseen=n:findLastTime()
n.datafile=n:generateDataFileName()
return n
end
function Neighbor:hasIP()
2018-07-19 19:19:29 -06:00
return not (self.ip==nil)
end
function Neighbor:hasHostName()
2018-07-19 19:19:29 -06:00
return not (self.hostname==nil)
end
function Neighbor:getExistingDataFileName()
2018-07-19 19:19:29 -06:00
local efn=nil
for fn in nxo.fs.glob(tmpdir.."/"..self.mac.."-*") do
efn=nxo.fs.basename(fn)
end
return efn
end
function Neighbor:generateDataFileName()
2018-07-19 19:19:29 -06:00
local tmpfn=nil
local efn=self:getExistingDataFileName()
if efn~=nil then
-- existing file found, improve or use
tmpfn=self.mac.."-"
m,x=string.match(efn,"^([%x2:]*)%-(.*)")
if x==nil or x=="" then -- "00:11:22:33:44:55-"
if self:hasHostName() then
tmpfn=tmpfn..self.hostname
nxo.fs.rename(tmpdir.."/"..efn,tmpdir.."/"..tmpfn)
elseif self:hasIP() then
tmpfn=tmpfn..self.ip
nxo.fs.rename(tmpdir.."/"..efn,tmpdir.."/"..tmpfn)
end
elseif get_ip_type(x)==1 then -- "00:11:22:33:44:55-10.11.22.33"
if self:hasHostName() then
tmpfn=tmpfn..self.hostname
nxo.fs.rename(tmpdir.."/"..efn,tmpdir.."/"..tmpfn)
else
tmpfn=efn
end
else -- "00:11:22:33:44:55-MAYBE-GOOD-HOST"
if x==self.hostname then -- "00:11:22:33:44:55-SAME-HOST"
tmpfn=efn
else -- "00:11:22:33:44:55-NEW-HOST-NAME"
if self.hostname~=nil then
tmpfn=tmpfn..self.hostname
nxo.fs.rename(tmpdir.."/"..efn,tmpdir.."/"..tmpfn)
else
tmpfn=efn
end
end
end
else
-- no prior file, let's generate one
tmpfn=self.mac.."-"
if self:hasHostName() then
tmpfn=tmpfn..self.hostname
elseif self:hasIP() then
tmpfn=tmpfn..self.ip
end
end
return tmpfn
end
function Neighbor:findLastTime()
2018-07-19 19:19:29 -06:00
self.lastseen=lasttime[mac]
return self.lastseen
end
function Neighbor:findHostname()
2018-07-19 19:19:29 -06:00
if self:hasIP() then
self.hostname=nslookup(self.ip)
end
return self.hostname
end
function Neighbor:findIp()
2018-07-19 19:19:29 -06:00
-- lookup IP from arpcache
local myarp=arpcache[self.mac]
if myarp~=nil then
self.ip=myarp["IP address"]
end
return self.ip
end
function Neighbor:findSignal()
2018-07-19 19:19:29 -06:00
-- lookup from iwinfo
self.signal=stations[self.mac:upper()].signal
return self.signal
end
function Neighbor:findNoise()
2018-07-19 19:19:29 -06:00
-- lookup from iwinfo
self.noise=stations[self.mac:upper()].noise
return self.noise
end
function Neighbor:findTxMcs()
2018-07-19 19:19:29 -06:00
self.tx_mcs=stations[self.mac:upper()].tx_mcs or -1
return self.tx_mcs
end
function Neighbor:findTxRate()
2018-07-19 19:19:29 -06:00
local r=(stations[self.mac:upper()].tx_rate)/1000
self.tx_rate=adjust_rate(r,bandwidth)
return self.tx_rate
end
function Neighbor:findRxMcs()
2018-07-19 19:19:29 -06:00
self.rx_mcs=stations[self.mac:upper()].rx_mcs or -1
return self.rx_mcs
end
function Neighbor:findRxRate()
2018-07-19 19:19:29 -06:00
local r=(stations[self.mac:upper()].rx_rate)/1000
self.rx_rate=adjust_rate(r,bandwidth)
return self.rx_rate
end
function Neighbor:log()
2018-07-19 19:19:29 -06:00
local oktolog=true
if self.lastseen ~= nil then
if stations[self.mac:upper()].inactive >= INACTIVETIMEOUT then
-- beacons expired
self.signal="null"
if nulledout[self.mac] == "true" then
-- No need to double log inactive null's
oktolog=false
end
end
end
if self.signal==0 then
oktolog=false
if nulledout[self.mac] == nil then
-- First time we have seen this show up
-- but it is at 0 wont be logged but will
-- end up in snrcache
nulledout[self.mac]="true"
end
end
if oktolog then
nulledout[self.mac]="false"
-- log neighbor data to datafile
local f, err=assert(io.open(tmpdir.."/"..self.datafile, "a"),"Cannot open file ("..tmpdir.."/"..self.datafile..") for appending!")
if f~=nil then
local now=os.date("%m/%d/%Y %H:%M:%S",os.time())
local outline=string.format("%s,%s,%s,%s,%s,%s,%s\n",now,self.signal,self.noise,self.tx_mcs,self.tx_rate,self.rx_mcs,self.rx_rate)
f:write(outline)
f:close()
if self.signal == "null" then
nulledout[self.mac]="true"
end
else
print(err)
end
end
return oktolog
end
-- Neighbor Class END
-- MAIN() -------------------------------------------------------------------------------------
-- check to make sure a prior instance is not still running
local f = io.open(pidfile,"r")
if (f) then
local oldpid = f:read("*number")
f:close()
if (oldpid ~= nill and dir_exists("/proc/" .. oldpid)) then
return
end
end
--- create pid file to communicate I'm running
f, err=assert(io.open(pidfile, "w"),"Cannot open file (pidfile) to write!")
if (f) then
local mypid = posix.unistd.getpid()
f:write(mypid)
f:close()
end
--- Do not run if prior period rssi_monitor is still running
local f = io.open(rssifile,"r")
if (f) then
local oldpid = f:read("*number")
f:close()
if (oldpid ~= nill and dir_exists("/proc/" .. oldpid)) then
os.remove(pidfile)
return
end
end
-- get wifi interface name
wifiiface=get_ifname("wifi")
-- load the lasttime table
if file_exists(lastdat) then
2018-07-19 19:19:29 -06:00
local f,err=io.open(lastdat,"r")
if f~=nil then
for line in f:lines() do
mac,last,nulled=string.match(line, "(.*)|(.*)|(.*)")
lasttime[mac]=last
nulledout[mac]=nulled
end
end
f:close()
end
-- get radio noise floor
local nf=iwinfo["nl80211"].noise(wifiiface)
if (nf < -101 or nf > -50) then
2018-07-19 19:19:29 -06:00
nf=defnoise
end
-- create tmp dir if needed
if not dir_exists(tmpdir) then
2018-07-19 19:19:29 -06:00
nxo.fs.mkdir(tmpdir)
end
-- get system uptime
--now=os.time()
now=luci.sys.uptime()
-- get all stations
stations=iwinfo["nl80211"].assoclist(wifiiface)
-- load up arpcache
arptable(
2018-07-19 19:19:29 -06:00
function(a)
arpcache[a["HW address"]:lower()]=a
end
)
-- get the current bandwidth setting
local radio=getMeshRadioDevice()
bandwidth=getChannelBW(radio)
-- iterate over all the stations
for mstation in pairs(stations) do
2018-07-19 19:19:29 -06:00
local n=Neighbor.create(mstation)
table.insert(neighbors,n)
end
-- get all the existing files in tmpdir
local tmpdirlist={}
for maclist in nxo.fs.dir(tmpdir) do
2018-07-19 19:19:29 -06:00
maclistbase=nxo.fs.basename(maclist)
table.insert(tmpdirlist,maclistbase)
end
-- neighbors loop START
for _,n in pairs(neighbors) do
2018-07-19 19:19:29 -06:00
-- trim datafile
file_trim(tmpdir.."/"..n.datafile,MAXLINES)
if n:log()==true then
lasttime[n.mac]=now
end
end
-- neighbors loop END
-- update snr.dat
local snrdatcache={}
if not file_exists(lastdat) then
2018-07-19 19:19:29 -06:00
-- create file
local f,err=assert(io.open(lastdat,"w+"),"Cannot create "..lastdat)
f:close()
end
for line in io.lines(lastdat) do
2018-07-19 19:19:29 -06:00
local mac,lastt,nulledoutprev=string.match(line,"(.*)|(.*)|(.*)")
if ((now-lastt) < AGETIME) then
-- keep it
snrdatcache[mac]=lastt
-- check if in neighbors table
local foundneighbor=false
for _,n in pairs(neighbors) do
if n.mac == mac then
foundneighbor=true
end
end
-- If wasn't previously nulled out and we don't have it in the neigbors table write a null
if nulledoutprev[mac] == "false" and foundneigbor == false then
-- find the log file name
for maclist in nxo.fs.glob(tmpdir.."/"..mac.."*") do
logdatafile=macclist
end
-- Write a null to the log file
local f, err=assert(io.open(logdatafile, "a"),"Cannot open file ("..logdatafile..") for appending!")
if f~=nil then
local now=os.date("%m/%d %H:%M:%S",os.time())
local outline=string.format("%s,%s,%s,%s,%s,%s,%s\n",now,'null',nf,'0','0','0','0')
f:write(outline)
f:close()
nulledout[mac]="true"
else
-- Don't log the null into SNRLog cause we were not successful
-- Though the assert() above should cause this too.
nulledout[mac]="false"
end
end
else
-- find the file and purge it
for maclist in nxo.fs.glob(tmpdir.."/"..mac.."*") do
nxo.fs.remove(maclist)
end
end
end
for _,n in pairs(neighbors) do
2018-07-19 19:19:29 -06:00
snrdatcache[n.mac]=now
end
-- re-write snr.dat file
local f,err=assert(io.open(lastdat,"w+"),"Cannot overwrite "..lastdat)
for k,v in pairs(snrdatcache) do
2018-07-19 19:19:29 -06:00
f:write(string.format("%s|%s|%s\n",k,v,nulledout[k]))
end
f:close()
os.remove(pidfile)
-- END MAIN