#!/usr/bin/perl #use Cwd; use POSIX qw(ceil); use bignum ( p => -1 ); use File::Copy; use Getopt::Long; use Inline C; ### config ### ## defaults my $min = 52; #$min = $ARGV[0] if $ARGV[0]; my $max = 1108; #$max = $ARGV[1] if $ARGV[1]; my $start = 0; my $spacing = 1.2; # 1.2 Mhz interval between graphs, 2.4 Mhz bandwidth, 1/2 overlap. my $samplerate = 2400000; my $gain = 30; my @freqs; my $freqlist; # E4000 "52-1108,1248-2200" # $scriptdir is where graphfreq.py script is. A copy of pyrtlsdr is also there, # but you could just have it in your path. #my $scriptdir = '/home/superkuh/app_installs/gnuradio/pyrtlsdr/aug-2012/pyrtlsdr_mod'; my $scriptdir = '.'; # pwd #my $scriptdir = getcwd(); #my $script = "python $scriptdir/graphfreqs_offset.py"; my $script = "python $scriptdir/graphfreqs_gnuplot.py"; my $fasterscript = "python $scriptdir/graphfreqs_faster5.py"; my $fast = 0; # Use a known LTE Cell station to get frequency error for logs. my $enableLTECellScanner = 0; my $freqoffset_passed; ## webpage index autogeneration and upload (via rsync) my $enablewebpagegeneration = 0; # use -w or set to 1 to enable. my $webdir = '/home/superkuh/www/gnuradio/live'; # set $rsync to 0 to disable. #my $rsync = 'rsync -avi -e "ssh" --bwlimit=200 /home/you/www/gnuradio/live/ you@yourserver.com:/home/you/www/gnuradio/live/'; my $rsync = 0; my $htmlfile = "$webdir/index.html"; my $pointsize = "0.5"; ## cli arguments GetOptions( 'f1:s' => \$min, #minimum frequency 'f2:s' => \$max, #max 'flist:s' => \$flist, #a list of frequency pairs like, --flist "52-1108,1248-2200" 'g:i' => \$gain, 's:s' => \$spacing, # distance between center frequencies 'r:s' => \$samplerate, 'd1:s' => \$scriptdir, # where graphfreqs.py is 'd2:s' => \$webdir, # where to put the images and logs 'c:s' => \$enableLTECellScanner, # use frequency offset correction 'w' => \$enablewebpagegeneration, # generate an html webpage 'p' => \$gnuplotenabled, # generate gnuplot timeseries charts too 'start:s' => \$start, # not implemented yet 'm' => \$spectralmapenabled, # make an all frequency spectral map 'point:s', => \$pointsize, # size of the points in the spectral map plots 'mr:s' => \$ranges, # takes comma seperated list of ranges for "52-1108,1248-2200" spectral maps. 'fast' => \$fast # enables fast mode, no spectrograms, just data taking and spectral maps. ); # 1,000,000 ### end config ### if ($flist) { print "Running graphfreq batch jobs $flist Mhz at $spacing Mhz spacings.\n"; } elsif ($fast) { print "Running graphfreq in non-batch fast mode $min to $max Mhz at $spacing Mhz spacings.\n Spectrograms and text output disabled.\n"; $script = $fasterscript; } else { print "Running graphfreq batch job $min to $max Mhz at $spacing Mhz spacings.\n"; } # um, okay, yeah, here's fine. Let's do this here. $spacing =int($spacing*10e5); if ($enableLTECellScanner) { my $ltefreq = $enableLTECellScanner; $freqoffset = getfreqoffset($ltefreq); $freqoffset =~ /(-?\d+\.?\d?)\w/; { no bignum; $freqoffset_passed = $1; # remove Kilo $freqoffset_passed *= '1000'; # add Kilo $ltefreq = $enableLTECellScanner * 1000000; # print "\n$freqoffset_passed / $ltefreq"; $freqoffset_passed = (($freqoffset_passed/($ltefreq))*1000000) # hertz to ppm } $freqoffset_passed = int($freqoffset_passed); } else { $freqoffset_passed = 0; } sub getfreqoffset { my ($freq) = @_; print "Using LTE Cell Scanner to find frequency offset from $freq Mhz station..."; $freq .= "e6"; my $command = "CellSearch -v -s $freq -e $freq"; my $info = `$command`; $info =~ /CrystalCorrectionFactor\n\d+\s+\w+\s+(-?\d+\.?\d?\w)/; my $freqoffset = $1; return $freqoffset; } print "$freqoffset frequency offset. Correcting $freqoffset_passed PPM.\n" if $enableLTECellScanner; # if all.log exists in $webdir, use it with gnuplot to generate a spectral map first. spectralmap() if $spectralmapenabled; # generate combined logs for arbitrary spectral maps, then plot them. File names are ~ spectral-map_$ranges.png arbitraryspectralmap($ranges) if $ranges; print "\n"; if ($flist) { # frequencylist($flist); @freqs = frequencylist($flist); oldstyle(); } elsif ($fast) { die "You need to set -f1 and -f2 frequencies to use fast mode\n" unless ($min and $max); $min1 = sprintf("%.0f", $min*1000000); $max1 = sprintf("%.0f", $max*1000000); my $command = "$script $min1 $samplerate $gain $freqoffset_passed $webdir $max1 $spacing"; print "$command\n"; $test = `$command`; } else { for ($min; $min < $max; $min += $spacing) { $rounded = sprintf("%.0f", $min*1000000); push(@freqs,$rounded); # @freqs is global } oldstyle(); } webpagegeneration($enablewebpagegeneration); exit; sub oldstyle { # takes global list of frequencies, @freqs foreach $freq (@freqs) { # if ($start) { # if ($freq < $start) { # next; # } else { # $start = 0; # } # } # New style call: freq samp gain ppm dir freq2 # python graphfreqs_faster3.py 40000000 2400000 30 -58 /tmp/faster 1700000000 my $command = "$script $freq $samplerate $gain $freqoffset_passed $webdir"; print "$command\n"; # `$command`; # use eval wrapped timer instead to prevent freezing due to children. eval { local $SIG{ALRM} = sub {die "alarm\n"}; alarm 10; $test = `$command`; alarm 0; }; if ($@) { die unless $@ eq "alarm\n"; print "Dongle froze, reseting it's USB device...\n"; donglefrozen(); } gnuplot($freq) if $gnuplotenabled; } } sub frequencylist { my $list = shift; my @pairs = split(",",$list); my @temp; foreach my $range (@pairs) { my ($min1,$max1) = split('-',$range); for ($min1; $min1 < $max1; $min1 += $spacing) { $rounded = sprintf("%.0f", $min1*1000000); push(@temp,$rounded); # @freqs is global } } return @temp; } sub frequencylist_old { my $list = shift; my @pairs = split(",",$list); foreach my $range (@pairs) { my ($min1,$max1) = split('-',$range); for ($min1; $min1 < $max1; $min1 += $spacing) { $rounded = sprintf("%.0f", $min1*1000000); push(@freqs,$rounded); # @freqs is global } } } sub webpagegeneration { exit unless $enablewebpagegeneration; print "Generating page, moving images.\n"; for my $file ( <$scriptdir/*.png> ) { move($file, "$webdir" ) or warn "Cannot copy $file: $!"; } open (my $html, ">", "$htmlfile") or die "Can't open $htmlfile to write.\n$!\n"; print $html "rtlsdr dongle frequency strength logger, 52-1000 Mhz\n"; print $html "\n"; my @files = <$webdir/*.png>; my @sortedfiles; foreach $file (@files) { $file =~ s#/home/superkuh/www/gnuradio/live/##; next if ($file =~ /spectral/); # don't show spectral map here. push(@sortedfiles,$file); } foreach $file (sort { $a <=> $b } @sortedfiles) { # print "html: $file\n"; # $file =~ /live\/(.*)/; # $file = $1; my $htmlfreq; next if ($file =~ /graph/); # don't show time series data directly. Link it. next if ($file =~ /spectral/); # don't show spectral map here. $file =~ /(.*)\.png/; $htmlname = $1; $htmlfreq = ($htmlname/1000000); if ($gnuplotenabled) { print $html "

\n"; } else { print $html "

\n"; } } print $html ""; close $html; print "\nstarting rsync...\n"; `$rsync` if $rsync; # I should put a function in here to parse the http logs for specific URLs encoding frequency scan requests. # Add them to @freqs at the end if they occured within $time seconds. That way users can generate output for # arbitrary frequencies. But, how do I differentiate them when making the index.html so they don't show up? } sub gnuplot { # it'd be nice if log filesize was checked before running to avoid errors when # the first run through a new set of frequencies is made. Like, if -f $frequency.log # < 1kB, next; my ($frequency) = @_; my $timestart; my $timeend; my @temp; # print "DEBUG: $webdir/$frequency\n"; open(INPUT, "$webdir/$frequency.log") or die "A horrible death.\n$!"; while () { # print; push @temp, $_; } close INPUT; # Get start and stop times $temp[0] =~ /(\d+.\d+?)/; $timestart = $1; $temp[-1] =~ /(\d+.\d+?)/; $timeend = $1; my $command = "set xdata time\n set timefmt \"\%s\"\n set format x \"\%j\"\n set xlabel \"time (day of year)\"\n set ylabel \"arbitrary power (dB)\"\n set key top left\n set key box\n set xrange [$timestart-946684800:$timeend-946684800]\n set datafile separator \",\"\n set xtics 86400\n set term png size 1000, 600\n set output \"$webdir/graph_$frequency.png\"\n plot \'$webdir/$frequency.log\' using 1:2 with linespoints\n"; # `echo $command | gnuplot`; open GP, '| gnuplot'; print GP $command; close GP; } sub spectralmap { print "Generating spectral map.\n"; my $freqdisplay; if ($flist) { $freqdisplay = $flist; } elsif ($fast) { $freqdisplay = "$min to $max"; } else { $freqdisplay = "$min to $max"; } my $ytics = 100000000; # ytics 100000000 , but what if the frequency range is funky? # this only handles mhz if ( (($max*10e5)-($min*10e5)) < 100000000 ) { $ytics = ((($max*10e5)-($min*10e5))/10); $ytics = roundup($ytics,1000000); unless ($ytics) { $ytics = 100000000; } #print "ytics: $ytics\n"; } sub roundup { my $num = shift; my $roundto = shift || 1; return int(ceil($num/$roundto))*$roundto; } my $command = "set timefmt \"\%s\"\n set view map\n set terminal png crop\n set output '$webdir/spectral-map.png'\n set term png size 1200, 800\n set key off\n set title \"$freqdisplay Mhz\"\n set xlabel \'Time\'\n set ylabel \'Frequency (Hz)\'\n set cblabel \'Intensity (dB)\'\n set xdata time\n set format x \"\%d/\%m \\n\%H\" set ytics $ytics\n plot \'$webdir/all.log\' using 1:2:3 with points ps $pointsize pt 5 palette\n replot\n exit\n"; open GP, '| gnuplot'; print GP $command; close GP; } ############################### sub arbitraryspectralmap { my $listofranges = shift; my @lines; my @files = getfilesinrange($listofranges); my $outputlog = "$webdir/all_$ranges.log"; foreach my $file (@files) { $file =~ /$webdir\/(\d+)\.log/; my $freq = $1; my $log; # print "$file-$freq\n"; open(INPUT, $file) or die $!; while () { my $fucker = $_; next if $file =~ /all/; my ($time, $power) = split(",", $fucker); $log = "$time $freq $power\n"; push(@lines, $log) if $log; $log = undef; } close INPUT; } # print "Generating combined log with " . scalar(@lines) . " lines to put in:\n$outputlog\n"; open (OUTPUT, ">$outputlog") or die "Shit shit fucking shit.\n$!"; foreach my $line (@lines) { print OUTPUT $line; } close OUTPUT; generalized_spectralmap($outputlog,$listofranges); } sub getfilesinrange { my $list = shift; my @pairs = split(",",$list); my @files = <$webdir/*.log>; my @results; foreach my $range (@pairs) { my ($min1,$max1) = split('-',$range); $min1 *= 1000000; $max1 *= 1000000; foreach my $file (@files) { next if $file =~ /all/; $file =~ /$webdir\/(\d+)\.log/; my $freq = $1; # print "outside: $file\n"; if ($freq >= $min1 && $freq <= $max1) { # print "inside: $min1,$freq,$max1,$file\n"; push(@results, $file); } } } return @results; } sub generalized_spectralmap { my ($logfile,$range) = @_; print "Generating another spectral map over only $range.\n"; my $command = "set timefmt \"\%s\"\n set view map\n set terminal png crop\n set output '$webdir/spectral-map_$range.png'\n set term png size 1200, 800\n set key off\n set title \"$range Mhz\"\n set xlabel \'Time\'\n set ylabel \'Frequency (Hz)\'\n set cblabel \'Intensity (dB)\'\n set xdata time\n set format x \"\%m/\%d\"\n set ytics 100000000\n plot \'$logfile\' using 1:2:3 with points ps $pointsize pt 5 palette\n replot\n exit\n"; open GP, '| gnuplot'; print GP $command; close GP; } ############################### sub donglefrozen { # calls inline::c code at the end, resetusb(), to reset the dongle if it freezes. # alternately you can compile the code in the link below and call the binary. # http://askubuntu.com/questions/645/how-do-you-reset-a-usb-device-from-the-command-line my $usbreset; my @devices = split("\n",`lsusb`); foreach my $line (@devices) { if ($line =~ /\w+\s(\d+)\s\w+\s(\d+):.+Realtek Semiconductor Corp\./) { # $usbreset = "sudo ./usbreset /dev/bus/usb/$1/$2"; $usbreset = "/dev/bus/usb/$1/$2"; resetusb($usbreset); } } } __END__ __C__ #include #include #include #include #include #include int resetusb(char *dongleaddress) { const char *filename; int fd; int rc; filename = dongleaddress; fd = open(filename, O_WRONLY); if (fd < 0) { perror("Error opening output file"); return 1; } printf("Resetting USB device %s\n", filename); rc = ioctl(fd, USBDEVFS_RESET, 0); if (rc < 0) { perror("Error in ioctl"); return 1; } printf("Reset successful\n"); close(fd); return 0; }