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

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

#159

  • Property svn:mime-type set to text/plain
File size: 11.1 KB
Line 
1#!/usr/bin/perl
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#-------------------------------------------------------------------------------
23
24use strict;
25use warnings;
26use Getopt::Std;
27use File::Basename;
28use Time::HiRes qw(gettimeofday);
29use POSIX qw(strftime);
30use POSIX qw(setsid);
31use LWP::UserAgent;
32use Device::SerialPort;
33
34# constants
35use constant {
36        OPS => 0b001,
37        DEBUG => 0b010,
38        TRACE => 0b100
39};
40# FIXME: increas that value
41my $MAX_READ_OP = 10000;
42my $MAX_CMD_WAIT = 3;
43
44# variables
45my %config;
46my %options;
47my $log;
48my $log_initialized = 0;
49my $flarm_initialized = 0;
50my $read_cnt = 0;
51my $flarm;
52
53# defaults
54my $log_ops = 1;
55my $log_debug = 0;
56my $log_trace = 0;
57my $interval = 3;
58my $skip = 1;
59
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};
72
73# display a help page
74sub usage {
75  print <<EOF;
76NAME
77  $0 -- stream flarm records to server
78
79SYNOPSIS
80  $0 [-c config_file] [-d] [-f data_file] [-i n] [-j m] [-r] [-t] [-h]
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               
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.                 
91               
92  -f    Read the data from the specified data file instead of the serial
93                device. This is mainly used for testing and development.
94
95  -i    Bundle records, send a request to the server every i-th GPGGA record.
96                Used for bandwidth optimization. Defaults to 3.
97               
98  -j    Send every j-th record to the server. Used for bandwidth
99                optimization. Defaults to 1.
100               
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.
104               
105  -h    Print this help.
106
107EOF
108  exit 0;
109}
110
111# a very basic logger
112sub logit {
113        my ($aspect, $msg) = @_;
114       
115        unless ($log_initialized) {
116                if (exists($config{'log'})) {
117                        open($log, ">> $config{'log'}") || die("Failed to open log file $config{'log'}: $!");
118                        select($log);
119                        $| = 1;
120                } else {
121                        $log = *STDOUT;
122                }
123                $log_initialized = 1;
124        }
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);
128}
129
130# read config file
131sub readconfig {
132        my ($config_file) = @_;
133        open(CONF, "< $config_file") || die("failed to open config file for reading: $!");
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
145# send a command to the flarm, optionally wait for the expected answer
146sub send_flarm_cmd {
147        my ($cmd, $expected_answer) = @_;
148        if (defined($flarm)) {
149                logit(OPS, "write to flarm cmd=" . $cmd);
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/) {
165                                        logit(OPS, "command accepted: " . $expected_answer);
166                                        last;
167                                }
168       
169                                my $elapsed = exact_time("%s") - $read_start;
170                                if ($elapsed > $MAX_CMD_WAIT) {
171                                        logit(OPS, "WARN: did not receive expected answer in time (" . $MAX_CMD_WAIT . " s)");
172                                        last;
173                                }
174                        }
175                }
176        }
177}
178
179# intialize the flarm device
180sub initialize_flarm_device {
181        logit(OPS, "initialize flarm device");
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
212        send_flarm_cmd("\$PFLAC,S,DEBUG,9", "PFLAC,S,DEBUG,9");
213       
214        $flarm_initialized = 1;
215        logit(OPS, "device initialization completed");
216}
217
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'})) {
223                        logit(OPS, "read flarm data from file " . $options{'f'});
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
232                        logit(OPS, "initialize serial port reader on device=" . $config{'serial_device'} . " with baud=" . $config{'baud'});
233                        $flarm = new Device::SerialPort ($config{'serial_device'}) || die "Can't open $config{'serial_device'}!\n";
234                        $flarm->baudrate($config{'baud'});
235                        $flarm->parity("none");
236                        $flarm->databits(8);
237                        $flarm->stopbits(1);
238                        $flarm->are_match("\r\n");
239                       
240                        if ($config{'do_device_config'}) {
241                                initialize_flarm_device() unless ($flarm_initialized);
242                        }
243                }
244        }
245       
246        # read data
247        my $record = undef;
248        if (defined($options{'f'})) {
249                $record = <$flarm>;
250        } else {
251                my $read_start = exact_time("%s");
252                until($record = $flarm->lookfor()) {
253                        logit(DEBUG, exact_time() . " wait for data from flarm device");
254                        sleep(1);
255                        $read_start = exact_time("%s");
256                }
257                my $read_duration = exact_time("%s") - $read_start;
258                if ($read_duration > 0.8) {
259                        logit(OPS, "trigger re-initialization of serial port, long read duration=" . $read_duration);
260                        $flarm = undef;
261                }
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                }
270        }
271        chomp($record);
272        return $record;
273}
274
275# as the name says
276sub exact_time {
277        my ($format) = @_;
278        $format = "%H:%M:%S" unless defined($format);
279        return strftime($format, localtime()) . "." . (gettimeofday())[1];
280}
281
282# send the records to the server. We don't make a request for each record for
283# performance reasons.
284sub flush {
285        my ($records, $url, $ua) = @_;
286        logit(DEBUG, exact_time() . " start flushing data to server");
287       
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);
290        my $resturl = $url . "/" . $date;
291        logit(DEBUG, exact_time() . " request resource: " . $resturl);
292       
293        # compose the request
294        my $request = HTTP::Request->new('PUT');
295        $request->url($resturl);
296        $request->header('stationKey'=>$config{'key'});
297        my $content = compress($records);
298        logit(DEBUG, exact_time() . " put on wire: " . $content);
299        $request->content($content);
300       
301        # run the request
302        logit(DEBUG, exact_time() . " start server push");
303        my $response = $ua->request($request);
304        logit(DEBUG, exact_time() . " end server push");
305       
306        # analyze the response
307        my $code = $response->code;
308        $response->code == 200 || logit(DEBUG, "error processing records (" . $response->code . ") records=[" . $records . "]");
309        logit(DEBUG, exact_time() . " end flushing data");
310}
311
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
324#
325# here starts our main program
326#
327
328# parse options
329getopts('c:di:j:f:th', \%options);
330
331my $cfile;
332if (defined($options{'c'})) {
333        $cfile = $options{'c'};
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";
339}
340
341# read config file
342die ("no config file found") unless (-f "$cfile");
343readconfig($cfile);
344
345if (defined($options{'d'})) {
346        $log_debug = 1;
347}
348if (defined($options{'i'})) {
349        $interval = $options{'i'};
350}
351if (defined($options{'j'})) {
352        $skip = $options{'j'}
353}
354if (defined($options{'h'})) {
355        usage();
356}
357if (defined($options{'t'})) {
358        $log_trace = 1;
359}
360
361# validation: key must be present in config file
362die("no key found in config file " . $cfile . " (option: key)") unless defined($config{'key'});
363
364# force a flush right away and after every write or print
365local $| = 1;
366
367# ok, everything is setup
368logit(OPS, "start client, connect to " . $config{'url'});
369
370# create UserAgent object
371my $ua = new LWP::UserAgent;
372
373my $buf;
374my $i = 0;
375
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) {
380                       
381                logit(TRACE, $record);
382                $buf = (defined($buf)) ? "$buf;$record" : $record;
383        }
384               
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;
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                }               
400        }
401}
402exit 0;
403
404END {
405        logit(OPS, "client is shutting down");
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.