Commit 06f10981 authored by Jenda's avatar Jenda
Browse files

scanner

parent 4377bbef
......@@ -26,9 +26,9 @@ COMPLEX64 = 8
class KukurukuScanner():
def __init__(self, l, confdir):
def __init__(self, l):
self.l = l
self.conf = util.ConfReader(confdir)
self.conf = util.ConfReader()
def croncmp(self, i, frag):
if frag == "*":
......@@ -86,9 +86,9 @@ class KukurukuScanner():
self.sdrflush()
peaks = []
for channel in cronframe.channels:
peaks.append((channel.freq-cronframe.freq, channel.bw, channel))
peaks.append([channel.freq-cronframe.freq, channel.bw, channel])
self.do_record(peaks, cronframe.cronlen, 1, None, cronframe)
self.do_record(peaks, 1, None, cronframe)
def scan(self, scanframe):
''' Find peaks in spectrum, record if specified in allow/blacklist '''
......@@ -121,7 +121,25 @@ class KukurukuScanner():
else:
if self.conf.dumpspectrum == "on_signal":
self.dump_spectrum(acc, self.getfn(scanframe.freq, None)+".spectrum.txt")
self.do_record(peaks, scanframe.stick, self.conf.filtermargin, sbuf, scanframe)
# determine whether we stumbled upon any specified channel which has PIPE set
peaks2 = []
for peak in peaks:
modified = False
for channel in scanframe.channels:
if channel.freq - scanframe.freq - channel.bw/2 < peak[0] and \
channel.freq - scanframe.freq + channel.bw/2 > peak[0]:
if channel.pipe:
peaks2.append([channel.freq - scanframe.freq, channel.bw, channel.pipe])
else:
peaks2.append([channel.freq - scanframe.freq, channel.bw])
modified = True
if not modified:
peaks2.append(peak)
self.do_record(peaks2, self.conf.filtermargin, sbuf, scanframe)
def filter_blacklist(self, peaks, center):
ret = []
......@@ -158,11 +176,12 @@ class KukurukuScanner():
frame.gain += diff
frame.gain = np.clip(frame.gain, self.conf.mingain, self.conf.maxgain)
def do_record(self, peaks, stoptime, safetymargin, buf, frame):
def do_record(self, peaks, safetymargin, buf, frame):
lastact = time.time()
center = frame.freq
floor = frame.floor
stoptime = time.time() + frame.stick
helpers = []
for peak in peaks:
......@@ -183,9 +202,9 @@ class KukurukuScanner():
ch.carry = '\0' * ch.cylen
basename = self.getfn(f+center, ch.rate)
if len(peak) >= 3 and peak[2].pipe is not None:
if len(peak) >= 3 and peak[2] is not None:
(ch.fd_r, ch.file) = os.pipe()
cmdline = peak[2].pipe.replace("_FILENAME_", basename)
cmdline = peak[2].replace("_FILENAME_", basename)
subprocess.Popen([cmdline], shell=True, stdin=ch.fd_r, bufsize=-1)
self.l.l("Recording \"%s\" (PIPE), firlen %i"%(cmdline, len(taps)), "INFO")
else:
......@@ -201,6 +220,7 @@ class KukurukuScanner():
# read data from sdr if needed
if buf is None:
buf = self.pipefile.read(self.conf.bufsize * COMPLEX64)
if frame.stickactivity:
acc = self.compute_spectrum(buf)
for peak in peaks:
......@@ -215,7 +235,8 @@ class KukurukuScanner():
int(ch.decim), ch.rotator, ch.rotpos, ch.firpos, ch.file)
ch.carry = buf[-ch.cylen:]
if time.time() > lastact + stoptime:
if ((not frame.stickactivity) and time.time() > stoptime) or \
(frame.stickactivity and time.time() > lastact + frame.silencegap):
self.l.l("Record stop", "INFO")
break
......@@ -235,11 +256,14 @@ class KukurukuScanner():
binhz = self.conf.rate/self.conf.fftw
startbin = int(peak[0]/binhz - peak[1]/(2*binhz))
stopbin = int(peak[0]/binhz + peak[1]/(2*binhz))
startbin = int(peak[0]/binhz - peak[1]/(2*binhz)) + self.conf.fftw/2
stopbin = int(peak[0]/binhz + peak[1]/(2*binhz)) + self.conf.fftw/2
print(acc)
for i in range(startbin, stopbin):
if acc[i] > floor:
print("acc %i lvl %f floor %f"%(i,acc[i], floor))
return True
return False
......@@ -263,13 +287,14 @@ class KukurukuScanner():
buf = buf*self.window
fft = np.absolute(np.fft.fft(buf))
fft = np.fft.fft(buf)
fft = (np.real(fft) * np.real(fft) + np.imag(fft) * np.imag(fft))/self.conf.fftw
fft = np.log10(fft)*10
acc += fft
iters += 1
acc = np.divide(acc, iters)
acc = np.log(acc)
# FFT yields a list of positive frequencies and then negative frequencies.
# We want it in the "natural" order.
......@@ -302,7 +327,7 @@ class KukurukuScanner():
if (i-first) >= minspan and (i-first) <= maxspan:
f = binhz*(((i+first)/2)-self.conf.fftw/2)
w = binhz*(i-first)
peaks.append((f, w))
peaks.append([f, w])
self.l.l("signal at %f width %f"%(f, w), "INFO")
return peaks
......
h 99700000 64000 bonton
[General]
freq=100000
floor=0.2
sql=0.3
cron=* 16 * * *
cronlen=60
randscan=no
stickactivity=false
[Channel1]
freq=99700
bw=64
;continue=30
;pipe=/home/jenda/tmp/kukuruku/client/modes/wfm.py -r 0.44
[Channel2]
freq=99300
bw=64
;continue=30
;pipe=/home/jenda/tmp/kukuruku/client/modes/wfm.py -r 0.44
[General]
; size of SDR buffer, used for flushing
bufsize=2048000
; SDR sample rate
rate=2048000
; the duration of one tune, in seconds
interval=5
; do not bother with scan if we have less than this number of seconds left
skip=3
; spectrum transform size
fftw=2048
; transform decimation
; we do transforms on every fftw*fftskip offset
fftskip=32
; when generating scanplan of frequency range, create at least this overlap between tunes
; e.g. if we are scanning from 100 MHz with 1MHz wire SDR, the first scan will be 99.8-100.8,
; the second 100.6 - 101.6 etc.
overlap=0.2
; when the scanner picks the next frequency, it computes SHA(time+nonce)
; set this to different values if you want multiple scanners jumping independently
nonce="abcdef"
; the noise floor is set as floor-quantile from power spectrum
floor=0.4
; treat everything higher than floor+sql as a signal
sql=0.5
; do not care about signals narrower than this (kHz) (e.g. random interference, carrier-only etc.)
minw=10
; do not care about signals wider than this (kHz) (e.g. do not catch 8 MHz wide DVB-T multiplex)
maxw=200
; initial gain
gain=0,30,30,30
; try to do autogain with this gain ID parameter
; -1 .. autogain disabled
; 0 .. libosmosdr autogain
; 1 .. libosmosdr RF gain
; 2 .. libosmosdr IF gain
; 3 .. libosmosdr BB gain
; all of them are hardware-specific, if you don't know, you probably want RF gain
messgain=1
; limit the auto gain algorithm -- it should not set gain below and above this
mingain=10
maxgain=49
; when generating FIR filters, make transition band bandwidth_of_signal*transition wide
transition=0.2
; multiply the filnal filter width by this factor
; we usually don't get the exact position of the signal, so we better make the
; filter wider and hope we dump it all...
filtermargin = 1.5
stickactivity = False
stick = 10
; never
; on_signal
; always
dumpspectrum = always
[General]
freq=172500
floor=0.2
sql=1.0
cron=* 2 * * *
cronlen=60
randscan=no
[Channel1]
freq=172650
bw=16
continue=30
[Channel2]
freq=172950
bw=16
continue=30
from libutil import Struct
def scanframe():
ScanframeT = Struct("scanframe", "freq floor squelch channels gain stickactivity pipe")
return ScanframeT(None, None, None, [], 0, False, None)
ScanframeT = Struct("scanframe", "")
return ScanframeT()
def cronframe():
CronframeT = Struct("cronframe", "freq floor squelch cronstr cronlen channels gain stickactivity pipe")
return CronframeT(None, None, None, "", 0, [], 0, False, None)
CronframeT = Struct("cronframe", "")
return CronframeT()
def channel():
ChannelT = Struct("channel", "freq bw cont")
return ChannelT(None, None, 0)
ChannelT = Struct("channel", "")
return ChannelT()
......@@ -35,10 +35,6 @@ class top_block(gr.top_block):
self.connect((self.osmosdr_source, 0), (self.blocks_file_descriptor_sink_0, 0))
# osmosdr.source.get_sample_rate() seems to be broken
def get_sample_rate(self):
return self.rate
def tune(self, f):
self.osmosdr_source.set_center_freq(int(f), 0)
......@@ -58,15 +54,14 @@ from KukurukuScanner import KukurukuScanner
import util
def usage():
print("Usage: %s [-d device] [-p ppm] [-r rate] -c confdir"%sys.argv[0])
print("Usage: %s [-d device] [-p ppm]"%sys.argv[0])
sys.exit(1)
ppm = 0
device = ""
confdir = None
try:
(opts, args) = getopt.getopt(sys.argv[1:], "d:c:p:")
(opts, args) = getopt.getopt(sys.argv[1:], "d:p:")
except getopt.GetoptError as e:
usage()
......@@ -75,23 +70,18 @@ device = ""
for opt, arg in opts:
if opt == "-d":
device = arg
elif opt == "-c":
confdir = arg
elif opt == "-p":
ppm = arg
else:
usage()
assert False
if not confdir:
usage()
l = util.logger()
l.setloglevel("DBG")
(fd_r,fd_w) = os.pipe()
scanner = KukurukuScanner(l, confdir)
scanner = KukurukuScanner(l)
sdr = top_block(device, scanner.conf.rate, ppm, fd_w)
......
......@@ -24,10 +24,13 @@ else:
filenames = os.listdir(".")
for filename in filenames:
if filename[-6:] != ".cfile":
fn = os.path.basename(filename)
if fn[-6:] != ".cfile":
continue
p = filename.split("-")
p = fn.split("-")
if len(p) != 8:
continue
......
......@@ -13,47 +13,58 @@ import time
from gnuradio.filter import firdes
import struct
from libutil import cfg_safe, engnum
MAINSECTION = "General"
class ConfReader():
def __init__(self, path):
def __init__(self):
confdir = os.path.join(os.path.expanduser('~'), ".kukuruku/scanner/")
# Read global config
rc = configparser.ConfigParser()
rc.read(os.path.join(path, "main.conf"))
self.bufsize = rc.getint(MAINSECTION, 'bufsize')
self.rate = rc.getint(MAINSECTION, 'rate')
self.interval = rc.getint(MAINSECTION, 'interval')
self.skip = rc.getint(MAINSECTION, 'skip')
self.fftw = rc.getint(MAINSECTION, 'fftw')
self.fftskip = rc.getint(MAINSECTION, 'fftskip')
self.overlap = rc.getfloat(MAINSECTION, 'overlap')
self.nonce = rc.get(MAINSECTION, 'nonce')
self.floor = rc.getfloat(MAINSECTION, 'floor')
self.sql = rc.getfloat(MAINSECTION, 'sql')
self.filtermargin = rc.getfloat(MAINSECTION, 'filtermargin')
self.transition = rc.getfloat(MAINSECTION, 'transition')
self.minw = rc.getint(MAINSECTION, 'minw')*1000
self.maxw = rc.getint(MAINSECTION, 'maxw')*1000
self.messgain = rc.getint(MAINSECTION, 'messgain')
self.mingain = rc.getint(MAINSECTION, 'mingain')
self.maxgain = rc.getint(MAINSECTION, 'maxgain')
self.gain = rc.get(MAINSECTION, 'gain')
self.stickactivity = rc.getboolean(MAINSECTION, 'stickactivity')
self.stick = rc.getint(MAINSECTION, 'stick')
self.dumpspectrum = rc.get(MAINSECTION, 'dumpspectrum')
cfpath = os.path.join(confdir, "main.conf")
rc.read(cfpath)
self.bufsize = cfg_safe(rc.getint, MAINSECTION, 'bufsize', 2048000, cfpath)
self.rate = cfg_safe(rc.getint, MAINSECTION, 'rate', 2048000, cfpath)
self.interval = cfg_safe(rc.getint, MAINSECTION, 'interval', 5, cfpath)
self.skip = cfg_safe(rc.getint, MAINSECTION, 'skip', 3, cfpath)
self.fftw = cfg_safe(rc.getint, MAINSECTION, 'fftw', 2048, cfpath)
self.fftskip = cfg_safe(rc.getint, MAINSECTION, 'fftskip', 32, cfpath)
self.overlap = cfg_safe(rc.getfloat, MAINSECTION, 'overlap', 0.2, cfpath)
self.nonce = cfg_safe(rc.get, MAINSECTION, 'nonce', "abcdef", cfpath)
self.floor = cfg_safe(rc.getfloat, MAINSECTION, 'floor', 0.4, cfpath)
self.sql = cfg_safe(rc.getfloat, MAINSECTION, 'sql', 0.5, cfpath)
self.filtermargin = cfg_safe(rc.getfloat, MAINSECTION, 'filtermargin', 1.5, cfpath)
self.transition = cfg_safe(rc.getfloat, MAINSECTION, 'transition', 0.2, cfpath)
self.minw = cfg_safe(rc.get, MAINSECTION, 'minw', 10000, cfpath)
self.maxw = cfg_safe(rc.get, MAINSECTION, 'maxw', 200000, cfpath)
self.messgain = cfg_safe(rc.getint, MAINSECTION, 'messgain', 1, cfpath)
self.mingain = cfg_safe(rc.getint, MAINSECTION, 'mingain', 10, cfpath)
self.maxgain = cfg_safe(rc.getint, MAINSECTION, 'maxgain', 49, cfpath)
self.gain = cfg_safe(rc.get, MAINSECTION, 'gain', "0,30,30,30", cfpath)
self.stickactivity = cfg_safe(rc.getboolean, MAINSECTION, 'stickactivity', False, cfpath)
self.stick = cfg_safe(rc.getint, MAINSECTION, 'stick', 10, cfpath)
self.silencegap = cfg_safe(rc.getint, MAINSECTION, 'silencegap', 5, cfpath)
self.dumpspectrum = cfg_safe(rc.get, MAINSECTION, 'dumpspectrum', "never", cfpath)
self.gainpcs = self.gain.split(",")
if len(self.gainpcs) != 4:
print("Wrong gain string format in configuration %s, should be \"N,N,N,N\""%self.gain)
sys.exit(1)
if self.messgain < 0 or self.messgain > 3:
print("messagin parameter must be an integer from range [0, 3]")
if self.messgain < -1 or self.messgain > 3:
print("messagin parameter must be an integer from range [-1, 3]")
sys.exit(1)
defaultgain = int(self.gainpcs[self.messgain])
files = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))]
self.minw = engnum(self.minw)
self.maxw = engnum(self.maxw)
self.rate = engnum(self.rate)
files = [f for f in os.listdir(confdir) if os.path.isfile(os.path.join(confdir, f))]
self.scanframes = []
self.cronframes = []
......@@ -62,7 +73,7 @@ class ConfReader():
# Read scanframe and channel config files
for f in files:
if f == "main.conf":
if f == "main.conf" or f == "blacklist.conf":
continue
if f[-5:] != ".conf":
continue
......@@ -70,32 +81,23 @@ class ConfReader():
print("file %s"%f)
rc = configparser.ConfigParser()
rc.read(os.path.join(path, f))
try:
floor = rc.getfloat(MAINSECTION, 'floor')
except:
floor = self.floor
rc.read(os.path.join(confdir, f))
try:
sql = rc.getfloat(MAINSECTION, 'sql')
except:
sql = self.sql
floor = cfg_safe(rc.getfloat, MAINSECTION, 'floor', self.floor)
sql = cfg_safe(rc.getfloat, MAINSECTION, 'sql', self.sql)
stickactivity = cfg_safe(rc.getboolean, MAINSECTION, 'stickactivity', self.stickactivity)
stick = cfg_safe(rc.getfloat, MAINSECTION, 'stick', self.stick)
silencegap = cfg_safe(rc.getfloat, MAINSECTION, 'silencegap', self.silencegap)
try:
stickactivity = rc.getboolean(MAINSECTION, 'stickactivity')
except:
stickactivity = self.stickactivity
if "freqstart" in rc.options(MAINSECTION): # range randscan
freqstart = rc.get(MAINSECTION, "freqstart")
freqstart = engnum(freqstart) + step/2
try:
stick = rc.getfloat(MAINSECTION, 'stick')
except:
stick = self.stick
freqstop = rc.get(MAINSECTION, "freqstop")
freqstop = engnum(freqstop) - step/2
if "freqstart" in rc.options(MAINSECTION): # range randscan
freqstart = rc.getint(MAINSECTION, "freqstart")*1000 + step/2
freqstop = rc.getint(MAINSECTION, "freqstop")*1000 - step/2
numf = int(math.ceil(float(freqstop - freqstart)/step))
if numf == 0: # freqstart == freqstop
numf = 1
delta = freqstop
......@@ -108,6 +110,7 @@ class ConfReader():
frm.floor = floor
frm.stickactivity = stickactivity
frm.stick = stick
frm.silencegap = silencegap
frm.sql = sql
frm.gain = defaultgain
......@@ -118,30 +121,27 @@ class ConfReader():
channels = self.channel_list_from_config(rc)
frm = cronframe()
frm.freq = rc.getint(MAINSECTION, "freq")*1000
frm.freq = engnum(rc.get(MAINSECTION, "freq"))
frm.floor = self.floor
frm.stickactivity = stickactivity
frm.stick = stick
frm.silencegap = silencegap
frm.sql = sql
frm.gain = defaultgain
frm.cron = rc.get(MAINSECTION, "cron")
frm.cronlen = rc.getint(MAINSECTION, "cronlen")
frm.channels = channels
self.cronframes.append(frm)
try:
randscan = rc.getboolean(MAINSECTION, "randscan")
except:
randscan = True
randscan = cfg_safe(rc.getboolean, MAINSECTION, "randscan", True)
if ((not ("freqstart" in rc.options(MAINSECTION))) and randscan):
frm = scanframe()
frm.freq = rc.getint(MAINSECTION, "freq")*1000
frm.freq = engnum(rc.get(MAINSECTION, "freq"))
frm.floor = floor
frm.stickactivity = stickactivity
frm.stick = stick
frm.silencegap = silencegap
frm.sql = sql
frm.gain = defaultgain
......@@ -152,8 +152,9 @@ class ConfReader():
# read blacklist
bl = []
if os.path.isfile("blacklist.txt"):
f = open("blacklist.txt")
blpath = os.path.join(confdir, "blacklist.conf")
if os.path.isfile(blpath):
f = open(blpath)
lines = f.readlines()
for line in lines:
pieces = line.strip().split(" ")
......@@ -174,8 +175,6 @@ class ConfReader():
elif self.blacklist[-1][1] == interval[0]:
self.blacklist[-1][1] = interval[1]
print(self.blacklist)
def channel_list_from_config(self, rc):
channels = []
......@@ -184,20 +183,12 @@ class ConfReader():
continue
ch = channel()
ch.freq = rc.getint(ssect, "freq")*1000
ch.bw = rc.getint(ssect, "bw")*1000
ch.freq = engnum(rc.get(ssect, "freq"))
ch.bw = engnum(rc.get(ssect, "bw"))
taps = firdes.low_pass(1, self.rate, ch.bw, ch.bw*self.transition, firdes.WIN_HAMMING)
ch.taps = struct.pack("=%if"%len(taps), *taps)
try:
ch.cont = rc.getint(ssect, "continue")
except:
ch.cont = 0
try:
ch.pipe = rc.get(ssect, 'pipe')
except:
ch.pipe = None
ch.pipe = cfg_safe(rc.get, ssect, 'pipe', None)
channels.append(ch)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment