ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/aor/aorscan
Revision: 1.9
Committed: Fri Oct 7 18:37:50 2005 UTC (18 years, 8 months ago) by root
Branch: MAIN
CVS Tags: HEAD
Changes since 1.8: +71 -55 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.9 my $record_vol = 7; # 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 root 1.9 # if (4096 < length $record_buf && $record_vol) {
102     # # do the agc adjustment
103     # my $pdl = PDL::Core::short (unpack "v*", substr $record_buf, -4096);
104     # 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 root 1.2
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 root 1.9 && !(@resp_jobs && $resp_jobs[-1]{exclusive})
210 root 1.2 ) {
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 root 1.9 my $job = $resp_jobs[0];
269    
270     my $exp = shift @{ $job->{exp} };
271 root 1.1
272 root 1.7 $exp eq substr $_, 0, 2
273     or die "sync error: expected '$exp', got '$_'\n";
274 root 1.1
275 root 1.9 push @{ $job->{res} }, $parse_resp{$exp}->();
276 root 1.2
277 root 1.9 unless (@{ $job->{exp} }) {
278 root 1.2 $job->{exclusive}->() if $job->{exclusive};
279     $job->{done}->send;
280    
281 root 1.9 shift @resp_jobs;
282    
283 root 1.1 send_scheduler;
284     }
285    
286     next;
287     }
288     });
289    
290     sub job::result {
291     my ($self) = @_;
292    
293     $self->{done}->wait;
294    
295     wantarray ? @{ $self->{res} } : $self->{res}[0]
296     }
297    
298     sub job {
299     my ($nice, $mode, $freq, @cmd) = @_;
300    
301     my $job = bless {
302 root 1.7 mode => $mode,
303 root 1.1 freq => round $freq,
304     cmd => \@cmd,
305     done => new Coro::Signal,
306 root 1.4 nice => $nice,
307 root 1.1 }, job::;
308    
309 root 1.7 unshift @cmd, tune $mode, $freq;
310    
311 root 1.2 if (@cmd && ref $cmd[-1]) {
312     $job->{exclusive} = pop @cmd;
313     }
314    
315 root 1.4 push @send_jobs, $job;
316     $send_requeue = 1;
317 root 1.1
318     send_scheduler;
319    
320     $job
321     }
322    
323 root 1.9 sub avg_lm {
324     my ($nice, $mode, $freq) = @_;
325    
326     (List::Util::sum +(job $nice, $mode, $freq, ((delay 0.010, 4), "LM=LM") x 4)->result) / 4
327     }
328    
329 root 1.2 sub aft($$$$) {
330     my ($nice, $mode, $center, $radius) = @_;
331 root 1.1
332     $radius = List::Util::max RESOLUTION, $radius * 0.1;
333    
334 root 1.7 my @level = map [$_->{freq}, (List::Util::sum $_->result) / 5],
335     map +(job $nice, $mode, $center + $radius * $_, ((delay 0.002, 4), "LM=LM") x 5),
336 root 1.1 -10 .. 10;
337    
338     my ($tune, $weight);
339     for (@level) {
340     my ($f, $w) = @$_;
341    
342     $w **= 8;
343    
344     $tune += $f * $w;
345     $weight += $w;
346     }
347    
348 root 1.6 (
349     int ($tune / $weight / RESOLUTION) * RESOLUTION,
350     int 0.5 + List::Util::max map $_->[1], @level,
351     )
352 root 1.1 }
353    
354     sub sweep {
355 root 1.2 my ($nice, $freqs, $cb) = @_;
356 root 1.1
357 root 1.4 my @jobs = map {
358     job $nice, $_->[0], $_->[1], "LM=LM";
359     } @$freqs;
360 root 1.1
361 root 1.7 $cb->($freqs->[$_], $jobs[$_]->result)
362 root 1.4 for 0 .. $#jobs;
363 root 1.2 }
364 root 1.1
365 root 1.2 #############################################################################
366 root 1.4 # raw search
367 root 1.2
368     async {
369     my $nice = 1000;
370 root 1.1
371 root 1.2 while () {
372 root 1.7 my @info = sql_fetchall "select mode, freq, width, activity_level from freq
373     where check_level < activity_level
374     and check_time < ?
375     order by check_time, mode, freq
376     limit 1000",
377     $NOW;
378 root 1.2
379 root 1.5 if (@info) {
380     sweep $nice, \@info, sub {
381 root 1.7 my ($info, $pre_lm) = @_;
382     my ($mode, $freq, $width, $activity_level) = @$info;
383 root 1.2
384 root 1.9 eval {
385     $pre_lm >= $activity_level
386     or die "pre_lm too low";
387    
388     my $avg_lm = avg_lm 0, $mode, $freq;
389    
390     warn "$mode:$freq plm $pre_lm alm $avg_lm\n";#d#
391    
392     $avg_lm >= $activity_level
393     or die "avg_lm too low";
394 root 1.2
395 root 1.7 my ($aft_freq, $aft_lm) = aft 0, $mode, $freq, $width * 0.9;
396 root 1.2
397 root 1.9 $aft_lm >= $activity_level
398     or die "aft_lm too low";
399    
400     print "$mode:$aft_freq($freq) $activity_level<= $pre_lm,$aft_lm ";
401 root 1.4
402 root 1.9 if ($freq - 0.5 * $width <= $aft_freq && $aft_freq <= $freq + 0.5 * $width) {
403     print "tuned\n";
404     sql_exec "update freq set check_time = ?, check_freq = ?, check_level = ?
405     where mode = ? and freq = ?",
406     $NOW + 86400*5 - (rand 86400), $aft_freq, $aft_lm,
407     $mode, $freq;
408 root 1.2 } else {
409 root 1.9 # outlier
410     print "outlier\n";
411     sql_exec "update freq set check_time = ?, check_freq = 0, check_level = 0
412 root 1.2 where mode = ? and freq = ?",
413 root 1.9 $NOW + 86400*7 - (rand 86400),
414 root 1.2 $mode, $freq;
415 root 1.9 }
416     };
417    
418     if ($@) {
419 root 1.2 # currently inactive
420 root 1.7 sql_exec "update freq set check_time = ?
421 root 1.2 where mode = ? and freq = ?",
422 root 1.7 $NOW,
423 root 1.2 $mode, $freq;
424     }
425 root 1.4 };
426 root 1.2 } else {
427 root 1.7 Coro::Timer::sleep 60;
428 root 1.2 }
429 root 1.1 }
430 root 1.2 };
431    
432     #############################################################################
433     # scan active freqs
434 root 1.1
435     async {
436 root 1.2 my $nice = 100;
437    
438 root 1.1 while () {
439 root 1.6 my $st = sql_exec \my ($mode, $freq, $width, $aft_freq, $activity_level),
440 root 1.7 "select mode, freq, width, check_freq, activity_level
441 root 1.2 from freq
442 root 1.7 where check_level >= activity_level and check_freq > 0";
443 root 1.2
444     if ($st->rows) {
445     my @jobs;
446    
447     while ($st->fetch) {
448 root 1.8 next if -e "/root/aor/$mode,$freq";
449    
450     push @jobs, [$mode, $freq, $activity_level,
451     job $nice, $mode, $aft_freq, "LM=LM"]
452 root 1.2 }
453    
454     for (@jobs) {
455 root 1.8 my ($mode, $freq, $activity_level, $job) = @$_;
456 root 1.2
457 root 1.7 my $lm = $job->result;
458 root 1.2
459 root 1.9 $lm >= $activity_level
460     or next;
461    
462     (avg_lm 0, $nice-1, $mode, $freq) >= $activity_level
463     or next;
464    
465     warn "record $job->{mode} $job->{freq} $lm\n";
466     (job $nice-1, $mode, $job->{freq}, "LM=LM", sub {
467 root 1.2 # (job $nice-1, 0, 98400000, "LM=LM", sub {
468 root 1.9 acmd $playback_on;
469     my $data = record_nsamples $record_rate * 10;
470     acmd $playback_off;
471    
472     open my $fh, ">:raw", "/root/aor/$mode,$freq"
473     or die "/root/aor/$mode,$freq: $!";
474     print $fh $data;
475     close $fh;
476 root 1.6
477 root 1.2 # my $data = 0.5 + (1 / 65536) * float unpack "v*", record_nsamples $record_rate * 0.1;
478 root 1.9 #$data = sin +(1/480) * xvals zeroes 48000;
479 root 1.2 # $data->reshape (100, $record_rate / 100);
480 root 1.9 #
481 root 1.2 # my $spectrum = 120 + cat map spectrum ($data->slice ("($_),"), "dB", "KAISER"), 0..99;
482     # printf "%s %s\n", (min $spectrum), (max $spectrum);
483     # imag short $spectrum;
484 root 1.9 })->result;
485 root 1.6
486 root 1.9 Coro::Timer::sleep 1;
487 root 1.2 }
488 root 1.1
489 root 1.7 Coro::Timer::sleep 5;
490 root 1.2 } else {
491 root 1.6 Coro::Timer::sleep 300;
492 root 1.2 }
493 root 1.1 }
494     };
495    
496 root 1.2 #############################################################################
497    
498 root 1.4 $| = 1;
499    
500 root 1.1 Coro::Event::loop;
501    
502     __END__
503     mysql -e "delete from freq" aor
504 root 1.2 ./aorscan --addrange 1 26565000 27405000 10000 25
505     ./aorscan --addrange 1 34360000 35800000 20000 25
506     ./aorscan --addrange 1 38460000 39840000 20000 25
507     ./aorscan --addrange 1 74215000 77455000 20000 25
508     ./aorscan --addrange 1 84015000 87255000 20000 24
509     ./aorscan --addrange 2 108000000 144000000 25000 20
510     ./aorscan --addrange 1 118000000 136000000 8333.3333333333333 24
511     ./aorscan --addrange 1 108000000 144000000 25000 24
512     ./aorscan --addrange 4 165210000 169380000 20000 24
513     ./aorscan --addrange 3 169810000 173980000 20000 24
514     ./aorscan --addrange 1 230000000 328000000 8333.3333333333333 10
515     ./aorscan --addrange 2 230000000 328000000 50000 20
516     ./aorscan --addrange 1 438650000 439425000 25000 10
517     ./aorscan --addrange 4 443600000 444962500 12500 10
518     ./aorscan --addrange 3 448600000 449962500 12500 10
519     ./aorscan --addrange 1 1270200000 1270700000 25000 10
520     ./aorscan --addrange 1 1298200000 1298700000 25000 10
521    
522     ./aorscan --addrange 3 2690000 2690000 25000 10
523     ./aorscan --addrange 3 3413000 3413000 25000 10
524     ./aorscan --addrange 4 3413000 3413000 25000 10
525     ./aorscan --addrange 3 5640000 5640000 25000 10
526     ./aorscan --addrange 4 5640000 5640000 25000 10
527     ./aorscan --addrange 3 8957000 8957000 25000 10
528     ./aorscan --addrange 4 8957000 8957000 25000 10
529     ./aorscan --addrange 3 13264000 13264000 25000 10
530     ./aorscan --addrange 4 13264000 13264000 25000 10
531 root 1.1