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

Last change on this file since 399 was 399, checked in by smoser, 10 years ago

remove duplicate copyright information

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