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