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

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

apply copyright

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