source: core/trunk/client/flarmclient.pl @ 312

Last change on this file since 312 was 312, checked in by smoser, 11 years ago

#159

  • Property svn:mime-type set to text/plain
File size: 11.2 KB
RevLine 
[310]1#!/usr/bin/perl
[296]2#-------------------------------------------------------------------------------
3# This file is part of the FLARM®-Radar Project.
4#   
5#   Copyright 2012-2014 Simon Moser
6#   Copyright 2013-2014 Dominic Spreitz
7#
8# Licensed under the Apache License, Version 2.0 (the "License");
9# you may not use this file except in compliance with the License.
10# You may obtain a copy of the License at
11#
12#   http://www.apache.org/licenses/LICENSE-2.0
13#
14# Unless required by applicable law or agreed to in writing, software
15# distributed under the License is distributed on an "AS IS" BASIS,
16# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17# See the License for the specific language governing permissions and
18# limitations under the License.
19#
20#   Project Website: www.flarmradar.ch
21#   Email: info@flarmradar.ch
22#-------------------------------------------------------------------------------
[130]23
24use strict;
25use warnings;
26use Getopt::Std;
27use File::Basename;
[224]28use Time::HiRes qw(gettimeofday);
[133]29use POSIX qw(strftime);
30use POSIX qw(setsid);
[130]31use LWP::UserAgent;
[278]32use Device::SerialPort;
[293]33
34# constants
[278]35use constant {
36        OPS => 0b001,
37        DEBUG => 0b010,
38        TRACE => 0b100
39};
[293]40# FIXME: increas that value
41my $MAX_READ_OP = 10000;
[310]42my $MAX_CMD_WAIT = 3;
[293]43
44# variables
[130]45my %config;
46my %options;
[278]47my $log;
48my $log_initialized = 0;
[310]49my $flarm_initialized = 0;
[293]50my $read_cnt = 0;
[288]51my $flarm;
[278]52
[293]53# defaults
[278]54my $log_ops = 1;
[282]55my $log_debug = 0;
[278]56my $log_trace = 0;
[251]57my $interval = 3;
58my $skip = 1;
[130]59
[278]60# signal handlers
61# toggle $log_trace with 'kill -USR1 <pid>'
62$SIG{USR1} = sub {
63        if ($log_trace) {
64                $log_trace = 0;
65        } else {
66                $log_trace = 1;
67        }
68};
69$SIG{INT} = sub {
70        die("terminate program");
71};
[130]72
[278]73# display a help page
[130]74sub usage {
75  print <<EOF;
76NAME
[278]77  $0 -- stream flarm records to server
[130]78
79SYNOPSIS
[278]80  $0 [-c config_file] [-d] [-f data_file] [-i n] [-j m] [-r] [-t] [-h]
[130]81
82DESCRIPTION
83  The following options are available:
84 
85  -c    Use the specified configuration file. Use the default configuration
86                file as a starting point for customization.
87               
[284]88  -d    Write out debug information
89                WARNING: If your configuration is to write the out put to a file (see
90                option 'log') then this might fill up the file system.                 
[224]91               
[284]92  -f    Read the data from the specified data file instead of the serial
93                device. This is mainly used for testing and development.
[253]94
[251]95  -i    Bundle records, send a request to the server every i-th GPGGA record.
96                Used for bandwidth optimization. Defaults to 3.
[212]97               
[251]98  -j    Send every j-th record to the server. Used for bandwidth
99                optimization. Defaults to 1.
100               
[284]101  -t    Trace client operations, write out all data read from the flarm device
102                WARNING: If your configuration is to write the out put to a file (see
103                option 'log') then this might fill up the file system.
[224]104               
[130]105  -h    Print this help.
106
107EOF
108  exit 0;
109}
110
[278]111# a very basic logger
[204]112sub logit {
[278]113        my ($aspect, $msg) = @_;
114       
[293]115        unless ($log_initialized) {
[279]116                if (exists($config{'log'})) {
[284]117                        open($log, ">> $config{'log'}") || die("Failed to open log file $config{'log'}: $!");
[293]118                        select($log);
119                        $| = 1;
[278]120                } else {
121                        $log = *STDOUT;
122                }
123                $log_initialized = 1;
[204]124        }
[278]125        print $log "$msg\n" if (($aspect & OPS) && $log_ops);
126        print $log "$msg\n" if (($aspect & TRACE) && $log_trace);
127        print $log "$msg\n" if (($aspect & DEBUG) && $log_debug);
[204]128}
129
[278]130# read config file
[130]131sub readconfig {
[278]132        my ($config_file) = @_;
133        open(CONF, "< $config_file") || die("failed to open config file for reading: $!");
[130]134        while(my $line = <CONF>) {
135                chomp($line);
136                next if $line =~ /^\s*#/;
137                next if $line =~ /^\s*$/;
138                if ($line =~ /^\s*(\S*)\s*=\s*(\S*)\s*$/) {
139                        $config{$1} = $2;
140                }
141        }
142        close(CONF);
143}
144
[293]145# send a command to the flarm, optionally wait for the expected answer
146sub send_flarm_cmd {
147        my ($cmd, $expected_answer) = @_;
[310]148        if (defined($flarm)) {
[311]149                logit(OPS, exact_time() . " write to flarm cmd=" . $cmd);
[310]150                my $count_out = $flarm->write($cmd . "\r\n");
151            warn "write failed\n" unless ($count_out);
152           
153                my $read_start = exact_time("%s");
154                my $record;
155               
156                if (defined $expected_answer) {
157                        while (1) {
158                                until($record = $flarm->lookfor()) {
159                                        logit(DEBUG, exact_time() . " wait for data from flarm device");
160                                        sleep(1);
161                                }
162                               
163                                logit(DEBUG, "read from flarm=" . $record);
164                                if ($record =~ /$expected_answer/) {
[311]165                                        logit(OPS, exact_time() . " command accepted: " . $expected_answer);
[310]166                                        last;
167                                }
168       
169                                my $elapsed = exact_time("%s") - $read_start;
170                                if ($elapsed > $MAX_CMD_WAIT) {
[311]171                                        logit(OPS, exact_time() . " WARN: did not receive expected answer in time (" . $MAX_CMD_WAIT . " s)");
[310]172                                        last;
173                                }
174                        }
175                }
176        }
[293]177}
178
[310]179# intialize the flarm device
180sub initialize_flarm_device {
[311]181        logit(OPS, exact_time() . " initialize flarm device");
[310]182       
183        # ACFT: type of aircraft, default 'unknown'
184        if (defined($config{'acft_type'})) {
185                send_flarm_cmd("\$PFLAC,S,ACFT," . $config{'acft_type'}, "PFLAC,A,ACFT");
186        }
187       
188        # NMEAOUT: which sentences are sent to the data port, default '1', all incl. propriatery
189        if (defined($config{'nmeaout'})) {
190                send_flarm_cmd("\$PFLAC,S,NMEAOUT," . $config{'nmeaout'}, "PFLAC,A,NMEAOUT");
191        }
192
193        # RANGE_ detection range of aircraft, default 25500, max value 25500
194        if (defined($config{'range'})) {
195                send_flarm_cmd("\$PFLAC,S,RANGE," . $config{'range'}, "PFLAC,A,RANGE");
196        }
197
198        # THRE: threshold for aircraft on ground, default '2'
199        if (defined($config{'threshold'})) {
200                send_flarm_cmd("\$PFLAC,S,THRE," . $config{'threshold'}, "PFLAC,A,THRE");
201        }
202       
203        # UI: visual and output, default '3' (disable buzzer, enable led warnings)
204        if (defined($config{'visual'})) {
205                send_flarm_cmd("\$PFLAC,S,UI," . $config{'visual'}, "PFLAC,A,UI");
206        }
207       
208        # no idea how it would behave if it was set to stealth, better switch off
209        send_flarm_cmd("\$PFLAC,S,PRIV,0", "PFLAC,A,PRIV");
210       
211        # not documented but this removes the vertical restriction
[312]212        send_flarm_cmd("\$PFLAC,S,DEBUG,9", "PFLAC,A,DEBUG,9");
[310]213       
214        $flarm_initialized = 1;
[311]215        logit(OPS, exact_time() . " device initialization completed");
[293]216}
217
[288]218# abstraction for reading data (from flarm device or from file)
219sub readdata {
220        unless (defined($flarm)) {
221                # initialize our flarm object
222                if (defined($options{'f'})) {
[311]223                        logit(OPS, exact_time() . " read flarm data from file " . $options{'f'});
[288]224                        open($flarm, "< $options{'f'}") || die("failed to open flarm data file for reading: $!");
225                } else {
226                        # check that we have the required configuration options
227                        die("missing option 'serial_device' in configuration file") unless (defined($config{'serial_device'}));
228                        die("device does not exist: " . $config{'serial_device'}) unless (-c $config{'serial_device'});
229                        die("missing option 'baud' in configuration file") unless (defined($config{'baud'}));
230                       
231                        # create serial device object
[311]232                        logit(OPS, exact_time() . " initialize serial port reader on device=" . $config{'serial_device'} . " with baud=" . $config{'baud'});
[289]233                        $flarm = new Device::SerialPort ($config{'serial_device'}) || die "Can't open $config{'serial_device'}!\n";
[288]234                        $flarm->baudrate($config{'baud'});
235                        $flarm->parity("none");
236                        $flarm->databits(8);
237                        $flarm->stopbits(1);
238                        $flarm->are_match("\r\n");
[289]239                       
[310]240                        if ($config{'do_device_config'}) {
241                                initialize_flarm_device() unless ($flarm_initialized);
242                        }
[288]243                }
244        }
245       
[293]246        # read data
[288]247        my $record = undef;
248        if (defined($options{'f'})) {
249                $record = <$flarm>;
250        } else {
[293]251                my $read_start = exact_time("%s");
[288]252                until($record = $flarm->lookfor()) {
[293]253                        logit(DEBUG, exact_time() . " wait for data from flarm device");
[288]254                        sleep(1);
[293]255                        $read_start = exact_time("%s");
[288]256                }
[293]257                my $read_duration = exact_time("%s") - $read_start;
[310]258                if ($read_duration > 0.8) {
[311]259                        logit(OPS, exact_time() . " trigger re-initialization of serial port, long read duration=" . $read_duration);
[310]260                        $flarm = undef;
261                }
[293]262               
263                # pro-active workaround to prevent failures during continuous operation
264                $read_cnt++;
265                if ($read_cnt >= $MAX_READ_OP) {
266                        logit(DEBUG, exact_time() . " reset serial port reader");
267                        $flarm = undef;
268                        $read_cnt = 0;
269                }
[288]270        }
271        chomp($record);
272        return $record;
273}
274
[278]275# as the name says
[224]276sub exact_time {
[293]277        my ($format) = @_;
278        $format = "%H:%M:%S" unless defined($format);
279        return strftime($format, localtime()) . "." . (gettimeofday())[1];
[224]280}
281
[130]282# send the records to the server. We don't make a request for each record for
283# performance reasons.
284sub flush {
[278]285        my ($records, $url, $ua) = @_;
286        logit(DEBUG, exact_time() . " start flushing data to server");
[224]287       
[293]288        my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
289    my $date= sprintf("%04d", ($year + 1900))."/".sprintf("%02d", ($mon + 1))."/".sprintf("%02d", $mday);
[130]290        my $resturl = $url . "/" . $date;
[278]291        logit(DEBUG, exact_time() . " request resource: " . $resturl);
[224]292       
293        # compose the request
[130]294        my $request = HTTP::Request->new('PUT');
295        $request->url($resturl);
296        $request->header('stationKey'=>$config{'key'});
[224]297        my $content = compress($records);
[278]298        logit(DEBUG, exact_time() . " put on wire: " . $content);
[224]299        $request->content($content);
300       
[130]301        # run the request
[278]302        logit(DEBUG, exact_time() . " start server push");
[130]303        my $response = $ua->request($request);
[278]304        logit(DEBUG, exact_time() . " end server push");
[224]305       
[130]306        # analyze the response
307        my $code = $response->code;
[278]308        $response->code == 200 || logit(DEBUG, "error processing records (" . $response->code . ") records=[" . $records . "]");
309        logit(DEBUG, exact_time() . " end flushing data");
[130]310}
311
[223]312# remove all unused records, debug information, etc.
313sub compress {
314        my ($records) = @_;
315        my $on_wire;
316        foreach my $record (split(';', $records)) {
317                if ($record =~ /^\$GPGGA,/ || $record =~ /^\$PFLAA,/) {
318                        $on_wire = (defined($on_wire)) ? $on_wire . ";" . $record : $record;
319                }               
320        }
321        return $on_wire;
322}
323
[278]324#
325# here starts our main program
326#
[261]327
[130]328# parse options
[251]329getopts('c:di:j:f:th', \%options);
[130]330
[278]331my $cfile;
[130]332if (defined($options{'c'})) {
333        $cfile = $options{'c'};
[278]334} elsif (-f "/etc/flarmclient.conf") {
335        $cfile = "/etc/flarmclient.conf";
336} else {
337        # last resort, search for a config file in the local directory
338        $cfile = "./flarmclient.conf";
[130]339}
[278]340
341# read config file
[283]342die ("no config file found") unless (-f "$cfile");
[278]343readconfig($cfile);
344
[224]345if (defined($options{'d'})) {
[278]346        $log_debug = 1;
[224]347}
[212]348if (defined($options{'i'})) {
349        $interval = $options{'i'};
350}
[251]351if (defined($options{'j'})) {
352        $skip = $options{'j'}
353}
[130]354if (defined($options{'h'})) {
355        usage();
356}
[224]357if (defined($options{'t'})) {
[278]358        $log_trace = 1;
[224]359}
[204]360
[130]361# validation: key must be present in config file
[251]362die("no key found in config file " . $cfile . " (option: key)") unless defined($config{'key'});
[130]363
364# force a flush right away and after every write or print
365local $| = 1;
366
[278]367# ok, everything is setup
[311]368logit(OPS, exact_time() . " start client, connect to " . $config{'url'});
[257]369
[278]370# create UserAgent object
371my $ua = new LWP::UserAgent;
[130]372
373my $buf;
[212]374my $i = 0;
[289]375
[288]376while(my $record = readdata()) {
377       
378        # send only n-th sequence to the server (option -s). A sequence is terminated with a GPGGA-record
379        if ($i % $skip == 0) {
[278]380                       
[288]381                logit(TRACE, $record);
382                $buf = (defined($buf)) ? "$buf;$record" : $record;
383        }
[251]384               
[288]385        # a GPGGA record terminates the sequence
386        if ($record =~ /^\$GPGGA,/) {
387                if ($i % ($interval * $skip) == 0) {
388                        flush($buf, $config{'url'}, $ua) ;
389                        $buf = undef;
[293]390                        if (defined($options{'f'})) {
391                                sleep($interval * $skip)
392                        } elsif ($i > $MAX_READ_OP) {
393                                # force a re-initialization
394                                $i = 0;
395                                $flarm = undef;
396                        }
397                } else {
398                        $i++;
399                }               
[130]400        }
401}
402exit 0;
403
[278]404END {
[311]405        logit(OPS, exact_time() . " client is shutting down");
[278]406        if (exists($config{"log"})) {
407                close($log);
408        }
409        undef $ua;
410        undef $flarm;
411}
412
Note: See TracBrowser for help on using the repository browser.