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

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

Update Copyright, fix encoding issue

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