File Coverage

lib/Net/GPSD/Server/Fake.pm
Criterion Covered Total %
statement 32 176 18.1
branch 2 94 2.1
condition 4 27 14.8
subroutine 9 23 39.1
pod 5 18 27.7
total 52 338 15.3


line stmt bran cond sub pod time code
1             package Net::GPSD::Server::Fake;
2              
3             =pod
4              
5             =head1 NAME
6              
7             Net::GPSD::Server::Fake - Provides a Fake GPSD daemon server test harness.
8              
9             =head1 SYNOPSIS
10              
11             use Net::GPSD::Server::Fake;
12             use Net::GPSD::Server::Fake::Stationary;
13             my $server=Net::GPSD::Server::Fake->new();
14             my $stationary=Net::GPSD::Server::Fake::Stationary->new(lat=>38.865826,
15             lon=>-77.108574);
16             $server->start($stationary);
17              
18             =head1 DESCRIPTION
19              
20             =cut
21              
22 1     1   877 use strict;
  1         2  
  1         43  
23 1     1   6 use vars qw($VERSION);
  1         2  
  1         47  
24 1     1   1611 use IO::Socket::INET;
  1         35270  
  1         9  
25 1     1   2090 use Time::HiRes qw{time};
  1         2060  
  1         4  
26 1     1   1144 use Geo::Functions qw{dm_deg};
  1         2468  
  1         3023  
27              
28             $VERSION = sprintf("%d.%02d", q{Revision: 0.16} =~ /(\d+)\.(\d+)/);
29              
30             =head1 CONSTRUCTOR
31              
32             =head2 new
33              
34             Returns a new server
35              
36             my $server=Net::GPSD::Server::Fake->new(
37             port=>'2947',
38             name=>'GPSD',
39             version=>Net::GPSD::Server::Fake->VERSION,
40             debug=>1); 0=>none, 2=>default, 2+=>verbose
41              
42             =cut
43              
44             sub new {
45 1     1 1 350 my $this = shift();
46 1   33     10 my $class = ref($this) || $this;
47 1         2 my $self = {};
48 1         4 bless $self, $class;
49 1         4 $self->initialize(@_);
50 1         2 return $self;
51             }
52              
53             =head1 METHODS
54              
55             =cut
56              
57             sub initialize {
58 1     1 0 1 my $self = shift();
59 1         3 my %param = @_;
60 1   50     9 $self->{'port'} = $param{'port'} || '2947';
61 1   33     4 $self->{'version'} = $param{'version'} || $VERSION;
62 1   50     6 $self->{'name'} = $param{'name'} || 'GPSD';
63 1 50       5 $self->{'debug'} = defined($param{'debug'}) ? $param{'debug'} : 2;
64             }
65              
66             =head2 start
67              
68             Binds provider to port and starts server.
69              
70             $server->start($provider);
71              
72             =cut
73              
74             sub start {
75 0     0 1 0 my $self=shift();
76 0         0 my $provider=shift();
77 0         0 $SIG{CHLD} = 'IGNORE';
78 0         0 my $listen_socket = IO::Socket::INET->new(LocalPort=>$self->port,
79             Listen=>10,
80             Proto=>'tcp',
81             Reuse=>1);
82              
83 0 0       0 die "Can't create a listening socket: $@" unless $listen_socket;
84 0 0       0 print "Debug Level: ", $self->{'debug'}, "\n" if ($self->{'debug'} > 2);
85              
86 0   0     0 while ($listen_socket->opened and my $connection=$listen_socket->accept) {
87 0         0 my $child;
88 0 0       0 die "Can't fork: $!" unless defined ($child = fork());
89 0 0       0 if ($child == 0) { #i'm the child!
90 0         0 $listen_socket->close; #only parent needs listening socket
91 0         0 my $chars="";
92 0         0 my $w=0;
93 0         0 my $r=0;
94 0         0 my $pid_watcher=undef();
95 0         0 my $pid_rmode=undef();
96 0         0 my $name=$self->name;
97 0         0 my $point=undef();
98 0         0 my $sockhost=$connection->sockhost;
99 0         0 my $sockport=$connection->sockport;
100 0         0 my $peerhost=$connection->peerhost;
101 0         0 my $peerport=$connection->peerport;
102 0 0       0 print "Connected: ", $sockhost, ":", $sockport, " -> ", $peerhost,":",$peerport, "\n" if ($self->{'debug'} > 0);
103 0         0 while (defined($_=$connection->getline)) {
104 0         0 chomp;
105 0 0       0 print "Command: ", $connection->peerhost,":",$connection->peerport, " -> ",$_ if ($self->{'debug'} > 1);
106 0 0       0 next unless m/\S/; # blank line
107 0         0 my @output=($name);
108 0         0 $point=$provider->get(time, $point);
109 0         0 my @list=parseline($_);
110 0         0 foreach (@list) {
111 0 0       0 print " => $_" if ($self->{'debug'} > 2);
112 0 0       0 if (m/l/i) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
113 0         0 push @output, "L=0 ".$self->version." ailopstvwxy ".ref($self);
114             } elsif (m/a/i) {
115 0         0 push @output, "A=".u2q($point->alt);
116 0 0       0 print ", A=".u2q($point->alt) if ($self->{'debug'} > 3);
117             } elsif (m/v/i) {
118 0         0 push @output, "V=".u2q($point->speed_knots);
119 0 0       0 print ", V=".u2q($point->speed_knots) if ($self->{'debug'} > 3);
120             } elsif (m/t/i) {
121 0         0 push @output, "T=".u2q($point->heading);
122 0 0       0 print ", T=".u2q($point->heading) if ($self->{'debug'} > 3);
123             } elsif (m/s/i) {
124 0         0 push @output, "S=".u2q($point->status);
125 0 0       0 print ", S=".u2q($point->status) if ($self->{'debug'} > 3);
126             } elsif (m/x/i) {
127 0   0     0 push @output, "X=". $point->time||0;
128             } elsif (m/i/i) {
129 0         0 push @output, "I=".u2q(ref($provider));
130             } elsif (m/m/i) {
131 0         0 push @output, "M=".u2q($point->mode);
132             } elsif (m/p/i) {
133 0         0 push @output, "P=".join(" ",
134             u2q($point->lat),
135             u2q($point->lon)
136             );
137             } elsif (m/o/i) {
138 0         0 push @output, $self->line_o($provider, $point);
139             } elsif (m/y/i) {
140 0         0 push @output, $self->line_y($provider, $point);
141             } elsif (m/w/i) {
142 0 0       0 $w=$w?0:1;
143 0         0 push @output, "W=$w";
144 0 0       0 if ($w) {
145 0         0 $pid_watcher=$self->start_watcher($connection, $provider);
146 0 0       0 print " => PID: $pid_watcher" if ($self->{'debug'} > 2);
147             } else {
148 0         0 $self->stop_child($pid_watcher);
149             }
150             } elsif (m/r/i) {
151 0 0       0 $r=$r?0:1;
152 0         0 push @output, "R=$r";
153 0 0       0 if ($r) {
154 0         0 $pid_rmode=$self->start_rmode($connection, $provider);
155 0 0       0 print " => PID: $pid_rmode" if ($self->{'debug'} > 2);
156             } else {
157 0         0 $self->stop_child($pid_rmode);
158             }
159             } else {
160             }
161             } #end of foreach
162 0         0 print $connection join(",", @output), "\n";
163 0 0       0 print "\n" if ($self->{'debug'} > 0);
164             } #end of while
165 0 0       0 print "Disconnected: ", $sockhost, ":", $sockport, " -> ", $peerhost,":",$peerport, "\n" if ($self->{'debug'} > 0);
166             } else { #i'm the parent
167 0         0 $connection->close();
168             }
169             }
170             }
171              
172             sub parseline {
173 0     0 0 0 my $line=shift();
174 0         0 my @list=();
175 0         0 while ($line=~s/([a-z][^a-z]*)//i) {
176 0 0       0 push(@list, $1) if $1;
177             }
178 0         0 return @list;
179             }
180              
181             sub start_watcher {
182 0     0 0 0 my $self=shift();
183 0         0 my $fh=shift();
184 0         0 my $provider=shift();
185 0         0 my $pid=fork();
186 0 0       0 die("Error: Cannot fork.") unless defined $pid;
187 0 0       0 if ($pid) {
188 0         0 return $pid;
189             } else {
190 0 0       0 print ", starting watcher" if ($self->{'debug'} > 4);
191 0         0 $self->watcher($fh, $provider);
192             }
193             }
194              
195             sub start_rmode {
196 0     0 0 0 my $self=shift();
197 0         0 my $fh=shift();
198 0         0 my $provider=shift();
199 0         0 my $pid=fork();
200 0 0       0 die("Error: Cannot fork.") unless defined $pid;
201 0 0       0 if ($pid) {
202 0         0 return $pid;
203             } else {
204 0         0 $self->rmode($fh, $provider);
205             }
206             }
207              
208             sub stop_child {
209 0     0 0 0 my $self=shift();
210 0         0 my $pid=shift();
211 0 0       0 print ", killing watcher" if ($self->{'debug'} > 4);
212 0         0 kill "HUP", $pid;
213             }
214              
215             sub line_o {
216 0     0 0 0 my $self=shift();
217 0         0 my $provider=shift();
218 0         0 my $point=shift();
219 0 0       0 if (ref($point) eq "Net::GPSD::Point") {
220             #print $fh $self->name,",O=",
221 0   0     0 return "O=".
      0        
      0        
222             join(" ", $point->tag||"FAKE", $point->time||time,
223             $point->errortime||0.001, u2q($point->lat), u2q($point->lon),
224             u2q($point->alt), u2q($point->errorhorizontal),
225             u2q($point->errorvertical), u2q($point->heading),
226             u2q($point->speed), u2q($point->climb),
227             u2q($point->errorheading), u2q($point->errorspeed),
228             u2q($point->errorclimb), u2q($point->mode));
229             } else {
230 0         0 die("Error: provider->get must return Net::GPSD::Point\n");
231             }
232             }
233              
234             sub line_y {
235 0     0 0 0 my $self=shift();
236 0         0 my $provider=shift();
237 0         0 my $point=shift();
238 0         0 my @satellite=$provider->getsatellitelist($point);
239 0 0       0 if (ref($satellite[0]) eq "Net::GPSD::Satellite") {
240             #print $fh $self->name,",Y=",
241 0         0 return "Y=".
242             join(":",
243             join(" ", "FAKE",$point->time, scalar(@satellite)),
244 0         0 map {join(" ", $_->prn, round($_->elev,1), round($_->azim,1),
245             round($_->snr,1), $_->used)
246             } @satellite);
247             } else {
248 0         0 die("Error: provider->getsatellitelist must return a list of Net::GPSD::Satellite objects.\n");
249             }
250             }
251              
252             sub line_rmc {
253 0     0 0 0 my $self=shift();
254 0         0 my $provider=shift();
255 0         0 my $point=shift();
256 0         0 my ($nd, $nm, $nsign)=dm_deg($point->lat, qw{N S});
257 0         0 my ($ed, $em, $esign)=dm_deg($point->lon, qw{E W});
258            
259 0         0 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime($point->time);
260 0 0       0 my $line=sprintf("GPRMC,%02d%02d%02d,%s,%02d%07.4f,%s,%03d%07.4f,%s,%.4f,%.3f,%02d%02d%02d,,",
261             $hour,
262             $min,
263             $sec,
264             $point->fix ? 'A' : 'V',
265             $nd,
266             $nm,
267             $nsign,
268             $ed,
269             $em,
270             $esign,
271             $point->speed_knots,
272             $point->heading,
273             $mday,
274             $mon + 1,
275             $year % 100);
276 0         0 return join('', '$', $line, '*', checksum($line));
277             }
278              
279             sub watcher {
280 0     0 0 0 my $self=shift();
281 0         0 my $fh=shift();
282 0         0 my $provider=shift();
283 0         0 my $point=undef();
284 0         0 my $count=0;
285              
286 0         0 while (1) {
287 0         0 $point=$provider->get(time(), $point);
288 0         0 print $fh join(",", $self->name, $self->line_o($provider, $point)), "\n";
289 0 0       0 if ($count++ % 5 == 0) {
290 0         0 print $fh join(",", $self->name, $self->line_y($provider, $point)), "\n";
291             }
292 0         0 sleep 1;
293             }
294             }
295              
296             sub rmode {
297 0     0 0 0 my $self=shift();
298 0         0 my $fh=shift();
299 0         0 my $provider=shift();
300 0         0 my $point=undef();
301 0         0 my $count=0;
302              
303 0         0 while (1) {
304 0         0 $point=$provider->get(time(), $point);
305 0         0 print $fh $self->line_rmc($provider, $point), "\n";
306             # if ($count++ % 5 == 0) {
307             # print $fh join(",", $self->name, $self->line_y($provider, $point)), "\n";
308             # }
309 0         0 sleep 1;
310             }
311             }
312              
313             =head2 name
314              
315             Gets or sets GPSD protocol name. This defaults to "GPSD" as some clients are picky.
316              
317             $obj->name('GPSD');
318             my $name=$obj->name;
319              
320             =cut
321              
322             sub name {
323 1     1 1 3 my $self = shift();
324 1 50       3 if (@_) { $self->{'name'} = shift() } #sets value
  0         0  
325 1         4 return $self->{'name'};
326             }
327              
328             =head2 port
329              
330             Returns the current TCP port.
331              
332             my $port=$obj->port;
333              
334             =cut
335              
336             sub port {
337 1     1 1 44 my $self = shift();
338 1         5 return $self->{'port'};
339             }
340              
341             =head2 version
342              
343             Returns the version that the GPSD deamon reports in the L command. This default to the version of the Net::GPSD::Server::Fake->VERSION package.
344              
345             my $obj=Net$obj->version;
346             my $version=$obj->version;
347              
348             =cut
349              
350             sub version {
351 0     0 1   my $self = shift();
352 0           return $self->{'version'};
353             }
354              
355             sub u2q {
356 0     0 0   my $value=shift();
357 0 0 0       return (!defined($value)||($value eq "")) ? "?" : $value;
358             }
359              
360             sub round {
361 0     0 0   my $number=shift();
362 0   0       my $round=shift()||0.01;
363 0           return $round * int($number/$round);
364             }
365              
366             sub checksum {
367             #${line}*{chk}
368 0     0 0   my $line=shift(); #GPRMC,053513,A,5331.6290,N,11331.8101,W,0.0000,0.000,150107,,
369 0           my $csum = 0;
370 0           $csum ^= unpack("C", $_) foreach (split("", $line));
371 0           return sprintf("%2.2X",$csum);
372             }
373              
374             1;
375              
376             __END__