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

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

#159

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