ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/aor/aorscan
Revision: 1.5
Committed: Sun Oct 2 10:05:28 2005 UTC (18 years, 8 months ago) by root
Branch: MAIN
Changes since 1.4: +14 -12 lines
Log Message:
*** empty log message ***

File Contents

# Content
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 use Coro::Event;
18 use Coro::Timer qw(sleep);
19 use Coro::Signal;
20
21 use PDL ();
22 #use PDL::FFTW;
23 #use PDL::Audio;
24 #use PDL::IO::Pic;
25
26 use PApp::SQL;
27
28 use strict;
29
30 sub BAUD() { 9600 }
31 sub RESOLUTION() { 50 } # in Hz
32 sub round($) { int ($_[0] / RESOLUTION + 0.5) * RESOLUTION }
33
34 sub AFT_INTERVAL_MIN() { 3333 }
35
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 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 my $pdl = PDL::short (unpack "v*", substr $record_buf, -4096);
80 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 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 my $NOW = time;
136
137 Event->timer (after => 0, interval => 1, hard => 1, prio => 3, cb => sub { $NOW = time });
138
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 acmd $record_init;
181 acmd $record_set, $record_vol;
182 acmd $playback_off;
183
184 my @send_jobs;
185 my $send_requeue;
186 my ($curfreq, $curmode);
187 my $pipeline = 4;
188
189 my $rbuf;
190 my @resp_jobs;
191
192 sub send_scheduler {
193 while (@send_jobs
194 && @resp_jobs < $pipeline
195 && (!@resp_jobs || !$resp_jobs[-1]{exclusive})
196 ) {
197 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
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 printf "\r%d:%010d %4d %4d ", $curmode, $curfreq, $job->{nice}, scalar @send_jobs;
223
224 $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 syswrite $aor, $cmd;
236
237 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 sysread $aor, $rbuf, 4096, length $rbuf;
247
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 my $job = shift @resp_jobs;
265
266 $job->{exclusive}->() if $job->{exclusive};
267 $job->{done}->send;
268
269 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 nice => $nice,
293 }, job::;
294
295 if (@cmd && ref $cmd[-1]) {
296 $job->{exclusive} = pop @cmd;
297 }
298
299 push @send_jobs, $job;
300 $send_requeue = 1;
301
302 send_scheduler;
303
304 $job
305 }
306
307 sub lm {
308 map +(hex substr $_, 2) & 0x7f, @_
309 }
310
311 sub aft($$$$) {
312 my ($nice, $mode, $center, $radius) = @_;
313
314 $radius = List::Util::max RESOLUTION, $radius * 0.1;
315
316 my @level = map [$_->{freq}, (List::Util::sum lm $_->result) / 5],
317 map +(job $nice, $mode, $center + $radius * $_, (delay 0.002, "LM=LM") x 5),
318 -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 my $lm = ($weight / @level) ** (1/8);
331
332 (int ($tune / $weight / RESOLUTION) * RESOLUTION, $lm * 100)
333 }
334
335 sub sweep {
336 my ($nice, $freqs, $cb) = @_;
337
338 my @jobs = map {
339 job $nice, $_->[0], $_->[1], "LM=LM";
340 } @$freqs;
341
342 $cb->($freqs->[$_], lm $jobs[$_]->result)
343 for 0 .. $#jobs;
344 }
345
346 #############################################################################
347 # raw search
348
349 async {
350 my $nice = 1000;
351
352 while () {
353 my @info = sql_fetchall "select mode, freq, activity_level from freq
354 where search_level < activity_level
355 order by search_time, mode, freq
356 limit 1000";
357
358 if (@info) {
359 sweep $nice, \@info, sub {
360 my ($info, $lm) = @_;
361
362 print "found new frequency $info->[0]:$info->[1] $lm>=$info->[2]\n"
363 if $lm >= $info->[2];
364
365 sql_exec "update freq set search_time = ?, search_level = ?
366 where mode = ? and freq = ?",
367 $NOW, $lm,
368 $info->[0], $info->[1];
369 };
370 } else {
371 Coro::Timer::sleep 60;
372 }
373 }
374 };
375
376 #############################################################################
377 # aft of active freqs
378
379 async {
380 my $nice = 500;
381
382 while () {
383 my @info = sql_fetchall
384 "select mode, freq, width, activity_level
385 from freq
386 where (search_level >= activity_level
387 or aft_time > 0)
388 and aft_time < ?
389 order by mode, freq
390 limit 100",
391 $NOW;
392
393 if (@info) {
394 sweep $nice, \@info, sub {
395 my ($info, $lm) = @_;
396 my ($mode, $freq, $width, $activity_level) = @$info;
397
398 if ($lm >= $activity_level) {
399 my ($tune, $weight) = aft $nice, $mode, $freq, $width * 0.9;
400
401 print "$mode:$tune($freq) $lm>=$activity_level $weight ";
402
403 if ($freq - 0.5 * $width <= $tune && $tune <= $freq + 0.5 * $width) {
404 print "tuned\n";
405 sql_exec "update freq set aft_time = ?, aft_freq = ?, aft_level = ?
406 where mode = ? and freq = ?",
407 $NOW + 86400*5 - (rand 86400), $tune, $weight,
408 $mode, $freq;
409 } else {
410 # outlier
411 print "outlier\n";
412 sql_exec "update freq set aft_time = ?, aft_level = 0
413 where mode = ? and freq = ?",
414 $NOW + 86400*7 - (rand 86400),
415 $mode, $freq;
416 }
417 } else {
418 # currently inactive
419 sql_exec "update freq set aft_time = ?
420 where mode = ? and freq = ?",
421 $NOW + 10,
422 $mode, $freq;
423 }
424 };
425 Coro::Timer::sleep 1;
426 } else {
427 Coro::Timer::sleep 10;
428 }
429 }
430 };
431
432 #############################################################################
433 # scan active freqs
434
435 async {
436 return;
437 my $nice = 100;
438
439 while () {
440 my $st = sql_exec \my ($mode, $freq, $width, $activity_level),
441 "select mode, aft_freq, width, activity_level
442 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 push @jobs, [$activity_level, job $nice, $mode, $freq, delay 0.040, "LM=LM"];
451 }
452
453 for (@jobs) {
454 my ($activity_level, $job) = @$_;
455
456 my @lm = lm $job->result;
457
458 if ($lm[0] >= $activity_level) {
459 warn "scan $job->{mode} $job->{freq} @lm\n";
460 (job $nice-1, $mode, $job->{freq}, "LM=LM", sub {
461 # (job $nice-1, 0, 98400000, "LM=LM", sub {
462 acmd $playback_on;
463 Coro::Timer::sleep 1;
464 # my $data = 0.5 + (1 / 65536) * float unpack "v*", record_nsamples $record_rate * 0.1;
465 acmd $playback_off;
466 #$data = sin +(1/480) * xvals zeroes 48000;
467 # $data->reshape (100, $record_rate / 100);
468 #
469 # my $spectrum = 120 + cat map spectrum ($data->slice ("($_),"), "dB", "KAISER"), 0..99;
470 # printf "%s %s\n", (min $spectrum), (max $spectrum);
471 # imag short $spectrum;
472 })->result;
473 }
474 }
475
476 Coro::Timer::sleep 60;
477 } else {
478 Coro::Timer::sleep 60;
479 }
480 }
481 };
482
483 #############################################################################
484
485 $| = 1;
486
487 Coro::Event::loop;
488
489 __END__
490 mysql -e "delete from freq" aor
491 ./aorscan --addrange 1 26565000 27405000 10000 25
492 ./aorscan --addrange 1 34360000 35800000 20000 25
493 ./aorscan --addrange 1 38460000 39840000 20000 25
494 ./aorscan --addrange 1 74215000 77455000 20000 25
495 ./aorscan --addrange 1 84015000 87255000 20000 24
496 ./aorscan --addrange 2 108000000 144000000 25000 20
497 ./aorscan --addrange 1 118000000 136000000 8333.3333333333333 24
498 ./aorscan --addrange 1 108000000 144000000 25000 24
499 ./aorscan --addrange 4 165210000 169380000 20000 24
500 ./aorscan --addrange 3 169810000 173980000 20000 24
501 ./aorscan --addrange 1 230000000 328000000 8333.3333333333333 10
502 ./aorscan --addrange 2 230000000 328000000 50000 20
503 ./aorscan --addrange 1 438650000 439425000 25000 10
504 ./aorscan --addrange 4 443600000 444962500 12500 10
505 ./aorscan --addrange 3 448600000 449962500 12500 10
506 ./aorscan --addrange 1 1270200000 1270700000 25000 10
507 ./aorscan --addrange 1 1298200000 1298700000 25000 10
508
509 ./aorscan --addrange 3 2690000 2690000 25000 10
510 ./aorscan --addrange 3 3413000 3413000 25000 10
511 ./aorscan --addrange 4 3413000 3413000 25000 10
512 ./aorscan --addrange 3 5640000 5640000 25000 10
513 ./aorscan --addrange 4 5640000 5640000 25000 10
514 ./aorscan --addrange 3 8957000 8957000 25000 10
515 ./aorscan --addrange 4 8957000 8957000 25000 10
516 ./aorscan --addrange 3 13264000 13264000 25000 10
517 ./aorscan --addrange 4 13264000 13264000 25000 10
518