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

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

fix copyright issue in client

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