ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/aor/aorscan
Revision: 1.8
Committed: Mon Oct 3 04:42:49 2005 UTC (18 years, 8 months ago) by root
Branch: MAIN
Changes since 1.7: +6 -4 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 #!/opt/bin/perl
2    
3     # http://www.beepworld.de/members81/frequenzen/flughaefen.htm
4     # http://www.dia.unisa.it/professori/ads/corso-security/www/CORSO-9900/a5/Netsec/netsec.html
5     # http://www.scnt01426.pwp.blueyonder.co.uk/Articles/RS-232/AR-8000.htm
6    
7     use List::Util;
8     use Array::Heap2;
9    
10     use Fcntl;
11     use POSIX;
12     use Time::HiRes;
13    
14     use Event;
15    
16     use Coro;
17 root 1.2 use Coro::Event;
18 root 1.1 use Coro::Timer qw(sleep);
19     use Coro::Signal;
20    
21 root 1.2 use PDL ();
22 root 1.6 use PDL::FFTW;
23     use PDL::Audio;
24     use PDL::IO::Pic;
25 root 1.2
26 root 1.1 use PApp::SQL;
27    
28 root 1.2 use strict;
29    
30 root 1.1 sub BAUD() { 9600 }
31     sub RESOLUTION() { 50 } # in Hz
32     sub round($) { int ($_[0] / RESOLUTION + 0.5) * RESOLUTION }
33 root 1.7 sub MD_DELAY() { 0.0010 } # delay after mode switch
34     sub RF_DELAY() { 0.0040 } # delay after freq change
35 root 1.1
36 root 1.2 sub AFT_INTERVAL_MIN() { 3333 }
37 root 1.1
38     $PApp::SQL::DBH = PApp::SQL::connect_cached __FILE__, "DBI:mysql:database=aor;mysql_read_default_file=/root/.my.cnf"
39     or die "unable to open mysql database aor";
40    
41 root 1.2 my $tty = $ARGV[0] || "/dev/ttyS0";
42    
43     sub AGC_MAX() { 26000 }
44    
45     my $record_rate = 48000;
46     my $record_init = "amixer sset Mic cap";
47     my $record_set = "amixer sset Capture {}%";
48     my $record_cmd = "arecord -traw -c1 -fS16_LE -r$record_rate 2>/dev/null";
49     my $playback_on = "amixer sset Mic unmute";
50     my $playback_off = "amixer sset Mic mute";
51     my $playback_set = "amixer sset Mic {}%";
52    
53 root 1.7 if ($ARGV[0] eq "--addrange") {
54     my (undef, $mode, $lo, $hi, $step, $level) = @ARGV;
55    
56     my $cnt;
57     while () {
58     my $f = $lo + $cnt++ * $step;
59    
60     last if $f > $hi;
61    
62     eval {
63     sql_exec "insert into freq (mode, freq, width, activity_level) values (?, ?, ?, ?)",
64     $mode, round $f, $step, $level;
65     };
66     }
67    
68     exit;
69     } elsif ($ARGV[0] eq "--daemon") {
70     # nop
71     } else {
72     # exit
73     }
74    
75 root 1.2 sub imag($) {
76     my ($pdl) = @_;
77     wpic $pdl, "/tmp/aor.pgm";
78     system "cv", "/tmp/aor.pgm";
79     }
80    
81     sub acmd($;$) {
82     my ($cmd, $arg) = @_;
83    
84     $cmd =~ s/\{\}/$arg/g;
85     system "$cmd >/dev/null";
86     }
87    
88     my $record_buf;
89 root 1.7 my $record_vol = 13; # 100
90 root 1.2
91     sub record($$) {
92     my ($data_cb, $done_cb) = @_;
93    
94     $record_buf = "";
95    
96     open my $fh, "-|", $record_cmd
97     or die "$record_cmd: $!";
98    
99     Event->io (fd => $fh, poll => 'r', cb => sub {
100     if (sysread $fh, $record_buf, 65536, length $record_buf) {
101     if (4096 < length $record_buf) {
102     # do the agc adjustment
103 root 1.6 my $pdl = PDL::Core::short (unpack "v*", substr $record_buf, -4096);
104 root 1.2 if (AGC_MAX < $pdl->abs->max) {
105     acmd $record_set, --$record_vol;
106     # printf "AGC %d $record_vol\n", max abs $pdl;
107     }
108     }
109    
110     unless ($data_cb->()) {
111     $_[0]->w->cancel;
112     $done_cb->();
113     }
114     } else {
115     $_[0]->w->cancel;
116     $done_cb->();
117     }
118     });
119     }
120    
121     sub record_nsamples($) {
122     my $bytes = $_[0] * 2;
123    
124     my $done = new Coro::Signal;
125    
126     record sub {
127     $bytes > length $record_buf
128     }, sub {
129     $done->send;
130     };
131    
132     $done->wait;
133    
134     substr $record_buf, -$bytes
135     }
136    
137     my $NOW = time;
138 root 1.1
139 root 1.2 Event->timer (after => 0, interval => 1, hard => 1, prio => 3, cb => sub { $NOW = time });
140 root 1.1
141     open my $aor, "+<:raw", $tty
142     or die "$tty: $!";
143    
144     {
145     my $tio = new POSIX::Termios;
146    
147     $tio->getattr (fileno $aor);
148    
149     $tio->setiflag (POSIX::IXOFF | POSIX::IXON);
150     $tio->setoflag (0);
151     $tio->setcflag (POSIX::CLOCAL | POSIX::CREAD | POSIX::CSTOPB | POSIX::CS8);
152     $tio->setlflag (0);
153     $tio->setispeed (&POSIX::B9600);
154     $tio->setospeed (&POSIX::B9600);
155    
156     $tio->setattr (fileno $aor);
157     }
158    
159     my %mode = (
160     0 => "WFM",
161     1 => "NFM",
162     2 => "AM",
163     3 => "USB",
164     4 => "LSB",
165     5 => "CW",
166     );
167    
168 root 1.7 my %parse_resp = (
169     LM => sub { 0x7f & hex substr $_, 2 },
170     );
171    
172 root 1.1 sub cmd($) {
173     # print ">$_[0]<\n";#d#
174     # fcntl $aor, F_SETFL, 0;
175     syswrite $aor, "$_[0]\015\012";
176     }
177    
178 root 1.7 # seconds, overhead
179     sub delay($;$) {
180     my $bytes = int $_[0] * (BAUD / 11) - $_[1];
181 root 1.1
182     " " x ($bytes - 2)
183     }
184    
185 root 1.7 sub tune($$) {
186     (
187     "MD$_[0]" . (delay MD_DELAY, 4),
188     "RF" . (round $_[1]) . (delay RF_DELAY, 4),
189     )
190     }
191    
192 root 1.1 cmd "MC1"; # mic off (0 = on, 1 = off, 2 = squelch)
193     cmd "ST000050"; # minimum stepsize
194 root 1.2 acmd $record_init;
195     acmd $record_set, $record_vol;
196     acmd $playback_off;
197 root 1.1
198     my @send_jobs;
199 root 1.4 my $send_requeue;
200 root 1.1 my ($curfreq, $curmode);
201 root 1.2 my $pipeline = 4;
202 root 1.1
203 root 1.7 my $resp_buf;
204 root 1.1 my @resp_jobs;
205    
206     sub send_scheduler {
207 root 1.2 while (@send_jobs
208     && @resp_jobs < $pipeline
209     && (!@resp_jobs || !$resp_jobs[-1]{exclusive})
210     ) {
211 root 1.4 if ($send_requeue) {
212     # re-sort queue
213     $send_requeue = 0;
214    
215     @send_jobs = sort {
216     $b->{nice} <=> $a->{nice}
217     || $b->{mode} <=> $a->{mode}
218     || $b->{freq} <=> $a->{freq};
219     } @send_jobs;
220     }
221    
222     my $job = pop @send_jobs;
223 root 1.1
224     my ($cmd, @exp);
225    
226     $job->{exp} = [];
227     $job->{res} = [];
228    
229     for (@{ $job->{cmd} }) {
230 root 1.7 if (/^MD/) {
231     next if $curmode eq $_;
232     $curmode = $_;
233     } elsif (/^RF/) {
234     next if $curfreq eq $_;
235     $curfreq = $_;
236     }
237    
238 root 1.1 push @exp, $1 if s/^(..)=//;
239     $cmd .= "$_\015\012";
240     }
241    
242 root 1.7 printf "\r%s %s %4d %4d ", $curmode, $curfreq, $job->{nice}, scalar @send_jobs;
243    
244 root 1.1 $job->{exp} = \@exp;
245     $job->{res} = [];
246    
247 root 1.2 syswrite $aor, $cmd;
248    
249 root 1.1 if (@exp) {
250     push @resp_jobs, $job;
251     } else {
252     $job->{done}->send;
253     }
254     }
255     }
256    
257     Event->io (fd => $aor, prio => 1, poll => 'r', cb => sub {
258 root 1.7 sysread $aor, $resp_buf, 4096, length $resp_buf;
259 root 1.1
260 root 1.7 while ($resp_buf =~ s/^([^\015\012]*)[\012\015]+//s) {
261     local $_ = $1;
262     s/[\012\015]//g;
263 root 1.1
264 root 1.7 next unless /^\S/;
265 root 1.1
266 root 1.7 @resp_jobs or die "out of sync: expected nothing, but got '$_'\n";
267 root 1.1
268     my $exp = shift @{ $resp_jobs[0]{exp} };
269    
270 root 1.7 $exp eq substr $_, 0, 2
271     or die "sync error: expected '$exp', got '$_'\n";
272 root 1.1
273 root 1.7 push @{ $resp_jobs[0]{res} }, $parse_resp{$exp}->();
274 root 1.1
275     unless (@{ $resp_jobs[0]{exp} }) {
276 root 1.2 my $job = shift @resp_jobs;
277    
278     $job->{exclusive}->() if $job->{exclusive};
279     $job->{done}->send;
280    
281 root 1.1 send_scheduler;
282     }
283    
284     next;
285     }
286     });
287    
288     sub job::result {
289     my ($self) = @_;
290    
291     $self->{done}->wait;
292    
293     wantarray ? @{ $self->{res} } : $self->{res}[0]
294     }
295    
296     sub job {
297     my ($nice, $mode, $freq, @cmd) = @_;
298    
299     my $job = bless {
300 root 1.7 mode => $mode,
301 root 1.1 freq => round $freq,
302     cmd => \@cmd,
303     done => new Coro::Signal,
304 root 1.4 nice => $nice,
305 root 1.1 }, job::;
306    
307 root 1.7 unshift @cmd, tune $mode, $freq;
308    
309 root 1.2 if (@cmd && ref $cmd[-1]) {
310     $job->{exclusive} = pop @cmd;
311     }
312    
313 root 1.4 push @send_jobs, $job;
314     $send_requeue = 1;
315 root 1.1
316     send_scheduler;
317    
318     $job
319     }
320    
321 root 1.2 sub aft($$$$) {
322     my ($nice, $mode, $center, $radius) = @_;
323 root 1.1
324     $radius = List::Util::max RESOLUTION, $radius * 0.1;
325    
326 root 1.7 my @level = map [$_->{freq}, (List::Util::sum $_->result) / 5],
327     map +(job $nice, $mode, $center + $radius * $_, ((delay 0.002, 4), "LM=LM") x 5),
328 root 1.1 -10 .. 10;
329    
330     my ($tune, $weight);
331     for (@level) {
332     my ($f, $w) = @$_;
333    
334     $w **= 8;
335    
336     $tune += $f * $w;
337     $weight += $w;
338     }
339    
340 root 1.6 (
341     int ($tune / $weight / RESOLUTION) * RESOLUTION,
342     int 0.5 + List::Util::max map $_->[1], @level,
343     )
344 root 1.1 }
345    
346     sub sweep {
347 root 1.2 my ($nice, $freqs, $cb) = @_;
348 root 1.1
349 root 1.4 my @jobs = map {
350     job $nice, $_->[0], $_->[1], "LM=LM";
351     } @$freqs;
352 root 1.1
353 root 1.7 $cb->($freqs->[$_], $jobs[$_]->result)
354 root 1.4 for 0 .. $#jobs;
355 root 1.2 }
356 root 1.1
357 root 1.2 #############################################################################
358 root 1.4 # raw search
359 root 1.2
360     async {
361     my $nice = 1000;
362 root 1.1
363 root 1.2 while () {
364 root 1.7 my @info = sql_fetchall "select mode, freq, width, activity_level from freq
365     where check_level < activity_level
366     and check_time < ?
367     order by check_time, mode, freq
368     limit 1000",
369     $NOW;
370 root 1.2
371 root 1.5 if (@info) {
372     sweep $nice, \@info, sub {
373 root 1.7 my ($info, $pre_lm) = @_;
374     my ($mode, $freq, $width, $activity_level) = @$info;
375 root 1.2
376 root 1.7 if ($pre_lm >= $activity_level) {
377     # print "found possible frequency $mode:$freq $pre_lm>=$activity_level\n";
378 root 1.2
379 root 1.7 my ($aft_freq, $aft_lm) = aft 0, $mode, $freq, $width * 0.9;
380 root 1.2
381 root 1.7 if ($aft_lm >= $activity_level) {
382     print "$mode:$aft_freq($freq) $activity_level<= $pre_lm,$aft_lm ";
383 root 1.4
384 root 1.7 if ($freq - 0.5 * $width <= $aft_freq && $aft_freq <= $freq + 0.5 * $width) {
385     print "tuned\n";
386     sql_exec "update freq set check_time = ?, check_freq = ?, check_level = ?
387     where mode = ? and freq = ?",
388     $NOW + 86400*5 - (rand 86400), $aft_freq, $aft_lm,
389     $mode, $freq;
390     } else {
391     # outlier
392     print "outlier\n";
393     sql_exec "update freq set check_time = ?, check_freq = 0, check_level = 0
394     where mode = ? and freq = ?",
395     $NOW + 86400*7 - (rand 86400),
396     $mode, $freq;
397     }
398 root 1.2 } else {
399 root 1.7 # currently inactive
400     print "$mode:$aft_freq($freq) $activity_level <= ($pre_lm, $aft_lm)\n";
401     sql_exec "update freq set check_time = ?
402 root 1.2 where mode = ? and freq = ?",
403 root 1.7 $NOW + 3600 + (rand 3600),
404 root 1.2 $mode, $freq;
405 root 1.7 };
406 root 1.2 } else {
407     # currently inactive
408 root 1.7 sql_exec "update freq set check_time = ?
409 root 1.2 where mode = ? and freq = ?",
410 root 1.7 $NOW,
411 root 1.2 $mode, $freq;
412     }
413 root 1.4 };
414 root 1.2 } else {
415 root 1.7 Coro::Timer::sleep 60;
416 root 1.2 }
417 root 1.1 }
418 root 1.2 };
419    
420     #############################################################################
421     # scan active freqs
422 root 1.1
423     async {
424 root 1.2 my $nice = 100;
425    
426 root 1.1 while () {
427 root 1.6 my $st = sql_exec \my ($mode, $freq, $width, $aft_freq, $activity_level),
428 root 1.7 "select mode, freq, width, check_freq, activity_level
429 root 1.2 from freq
430 root 1.7 where check_level >= activity_level and check_freq > 0";
431 root 1.2
432     if ($st->rows) {
433     my @jobs;
434    
435     while ($st->fetch) {
436 root 1.8 next if -e "/root/aor/$mode,$freq";
437    
438     push @jobs, [$mode, $freq, $activity_level,
439     job $nice, $mode, $aft_freq, "LM=LM"]
440 root 1.2 }
441    
442     for (@jobs) {
443 root 1.8 my ($mode, $freq, $activity_level, $job) = @$_;
444 root 1.2
445 root 1.7 my $lm = $job->result;
446 root 1.2
447 root 1.7 if ($lm >= $activity_level) {
448 root 1.8 warn "record $job->{mode} $job->{freq} $lm\n";
449 root 1.2 (job $nice-1, $mode, $job->{freq}, "LM=LM", sub {
450     # (job $nice-1, 0, 98400000, "LM=LM", sub {
451 root 1.7 acmd $playback_on;
452 root 1.6 my $data = record_nsamples $record_rate * 10;
453 root 1.7 acmd $playback_off;
454 root 1.6
455     open my $fh, ">:raw", "/root/aor/$mode,$freq"
456     or die "/root/aor/$mode,$freq: $!";
457     print $fh $data;
458     close $fh;
459    
460 root 1.2 # my $data = 0.5 + (1 / 65536) * float unpack "v*", record_nsamples $record_rate * 0.1;
461     #$data = sin +(1/480) * xvals zeroes 48000;
462     # $data->reshape (100, $record_rate / 100);
463     #
464     # my $spectrum = 120 + cat map spectrum ($data->slice ("($_),"), "dB", "KAISER"), 0..99;
465     # printf "%s %s\n", (min $spectrum), (max $spectrum);
466     # imag short $spectrum;
467     })->result;
468 root 1.6
469     Coro::Timer::sleep 1;
470 root 1.2 }
471     }
472 root 1.1
473 root 1.7 Coro::Timer::sleep 5;
474 root 1.2 } else {
475 root 1.6 Coro::Timer::sleep 300;
476 root 1.2 }
477 root 1.1 }
478     };
479    
480 root 1.2 #############################################################################
481    
482 root 1.4 $| = 1;
483    
484 root 1.1 Coro::Event::loop;
485    
486     __END__
487     mysql -e "delete from freq" aor
488 root 1.2 ./aorscan --addrange 1 26565000 27405000 10000 25
489     ./aorscan --addrange 1 34360000 35800000 20000 25
490     ./aorscan --addrange 1 38460000 39840000 20000 25
491     ./aorscan --addrange 1 74215000 77455000 20000 25
492     ./aorscan --addrange 1 84015000 87255000 20000 24
493     ./aorscan --addrange 2 108000000 144000000 25000 20
494     ./aorscan --addrange 1 118000000 136000000 8333.3333333333333 24
495     ./aorscan --addrange 1 108000000 144000000 25000 24
496     ./aorscan --addrange 4 165210000 169380000 20000 24
497     ./aorscan --addrange 3 169810000 173980000 20000 24
498     ./aorscan --addrange 1 230000000 328000000 8333.3333333333333 10
499     ./aorscan --addrange 2 230000000 328000000 50000 20
500     ./aorscan --addrange 1 438650000 439425000 25000 10
501     ./aorscan --addrange 4 443600000 444962500 12500 10
502     ./aorscan --addrange 3 448600000 449962500 12500 10
503     ./aorscan --addrange 1 1270200000 1270700000 25000 10
504     ./aorscan --addrange 1 1298200000 1298700000 25000 10
505    
506     ./aorscan --addrange 3 2690000 2690000 25000 10
507     ./aorscan --addrange 3 3413000 3413000 25000 10
508     ./aorscan --addrange 4 3413000 3413000 25000 10
509     ./aorscan --addrange 3 5640000 5640000 25000 10
510     ./aorscan --addrange 4 5640000 5640000 25000 10
511     ./aorscan --addrange 3 8957000 8957000 25000 10
512     ./aorscan --addrange 4 8957000 8957000 25000 10
513     ./aorscan --addrange 3 13264000 13264000 25000 10
514     ./aorscan --addrange 4 13264000 13264000 25000 10
515 root 1.1