File Coverage

blib/lib/AnyEvent/GPSD.pm
Criterion Covered Total %
statement 30 178 16.8
branch 0 70 0.0
condition 0 29 0.0
subroutine 10 27 37.0
pod 4 12 33.3
total 44 316 13.9


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             AnyEvent::GPSD - event based interface to GPSD
4              
5             =head1 SYNOPSIS
6              
7             use AnyEvent::GPSD;
8              
9             =head1 DESCRIPTION
10              
11             This module is an L user, you need to make sure that you use and
12             run a supported event loop.
13              
14             This module implements an interface to GPSD (http://gpsd.berlios.de/).
15              
16             You need to consult the GPSD protocol desription in the manpage to make
17             better sense of this module.
18              
19             =head2 METHODS
20              
21             =over 4
22              
23             =cut
24              
25             package AnyEvent::GPSD;
26              
27 1     1   945 use strict;
  1         2  
  1         32  
28 1     1   5 no warnings;
  1         2  
  1         34  
29              
30 1     1   5 use Carp ();
  1         11  
  1         14  
31 1     1   895 use Errno ();
  1         4020  
  1         25  
32 1     1   10 use Scalar::Util ();
  1         2  
  1         14  
33 1     1   988 use Geo::Forward ();
  1         9284  
  1         43  
34              
35 1     1   1851 use AnyEvent ();
  1         6540  
  1         26  
36 1     1   1085 use AnyEvent::Util ();
  1         12387  
  1         27  
37 1     1   6176 use AnyEvent::Socket ();
  1         24601  
  1         37  
38 1     1   1323 use AnyEvent::Handle ();
  1         9479  
  1         2356  
39              
40             our $VERSION = '1.0';
41              
42             =item $gps = new AnyEvent::GPSD [key => value...]
43              
44             Creates a (virtual) connection to the GPSD. If the C<"hostname:port">
45             argument is missing then C will be used.
46              
47             If the connection cannot be established, then it will retry every
48             second. Otherwise, the connection is put into watcher mode.
49              
50             You can specify various configuration parameters, most of them callbacks:
51              
52             =over 4
53              
54             =item host => $hostname
55              
56             The host to connect to, default is C.
57              
58             =item port => $port
59              
60             The port to connect to, default is C<2947>.
61              
62             =item min_speed => $speed_in_m_per_s
63              
64             Sets the mininum speed (default: 0) that is considered real for the
65             purposes of replay compression or estimate. Speeds below this value will
66             be considered 0.
67              
68             =item on_error => $cb->($gps)
69              
70             Called on every connection or protocol failure, reason is in C<$!>
71             (protocl errors are signalled via EBADMSG). Can be used to bail out if you
72             are not interested in retries.
73              
74             =item on_connect => $cb->($gps)
75              
76             Nornormally used: Called on every successful connection establish.
77              
78             =item on_response => $cb->($gps, $type, $data, $time)
79              
80             Not normally used: Called on every response received from GPSD. C<$type>
81             is the single letter type and C<$data> is the data portion, if
82             any. C<$time> is the timestamp that this message was received at.
83              
84             =item on_satellite_info => $cb->($gps, {satellite-info}...)
85              
86             Called each time the satellite info changes, also on first connect. Each
87             C hash contains at least the following members (mnemonic:
88             all keys have three letters):
89              
90             C holds the satellite PRN (1..32 GPS, anything higher is
91             wASS/EGNOS/MCAS etc, see L).
92              
93             C, C contain the elevation (0..90) and azimuth (0..359) of the satellite.
94              
95             C contains the signal strength in decibals (28+ is usually the
96             minimum value for a good fix).
97              
98             C contains either C<1> to indicate that this satellite was used for
99             the last position fix, C<0> otherwise. EGNOS/WAAS etc. satellites will
100             always show as C<0>, even if their correction info was used.
101              
102             The passed hash references are read-only.
103              
104             =item on_fix => $cb->({point})
105              
106             Called regularly (usually about once/second), even when there is no
107             connection to the GPSD (so is useful to update your idea of the current
108             position). The passed hash reference must I be modified in any way.
109              
110             If C is C<2> or C<3>, then the C<{point}> hash contains at least the
111             following members, otherwise it is undefined which members exist. Members
112             whose values are not known are C (usually the error values, speed
113             and so on).
114              
115             time when this fix was received (s)
116              
117             lat latitude (S -90..90 N)
118             lon longitude (W -180..180 E)
119             alt altitude
120              
121             herr estimated horizontal error (m)
122             verr estimated vertical error (m)
123              
124             bearing bearing over ground (0..360)
125             berr estimated error in bearing (degrees)
126             speed speed over ground (m/s)
127             serr estimated error in speed over ground (m/s)
128             vspeed vertical velocity, positive = upwards (m/s)
129             vserr estimated error in vspeed (m/s)
130              
131             mode 1 = no fix, 2 = 2d fix, 3 = 3d fix
132              
133             =back
134              
135             =cut
136              
137             sub new {
138 0     0 1   my $class = shift;
139 0           my $self = bless {
140             @_,
141             interval => 1,
142             fix => { time => AnyEvent->now, mode => 1 },
143             }, $class;
144              
145 0           $self->interval_timer;
146 0           $self->connect;
147              
148 0           $self
149             }
150              
151             sub DESTROY {
152 0     0     my ($self) = @_;
153              
154 0           $self->record_log;
155             }
156              
157             sub event {
158 0     0 0   my $event = splice @_, 1, 1, ();
159              
160             #warn "event<$event,@_>\n";#d#
161 0 0         if ($event = $_[0]{"on_$event"}) {
162 0           &$event;
163             }
164             }
165              
166             sub retry {
167 0     0 0   my ($self) = @_;
168              
169 0           delete $self->{fh};
170 0           delete $self->{command};
171              
172 0           Scalar::Util::weaken $self;
173             $self->{retry_w} = AnyEvent->timer (after => 1, cb => sub {
174 0     0     delete $self->{retry_w};
175 0           $self->connect;
176 0           });
177             }
178              
179             # make sure we send "no fix" updates when we lose connectivity
180             sub interval_timer {
181 0     0 0   my ($self) = @_;
182              
183             $self->{interval_w} = AnyEvent->timer (after => $self->{interval}, cb => sub {
184 0 0   0     if (AnyEvent->now - $self->{fix}{time} > $self->{interval} * 1.9) {
185 0           $self->{fix}{mode} = 1;
186 0           $self->event (fix => $self->{fix});
187             }
188              
189 0           $self->interval_timer;
190 0           });
191              
192 0           Scalar::Util::weaken $self;
193             }
194              
195             sub connect {
196 0     0 0   my ($self) = @_;
197              
198 0 0         return if $self->{fh};
199              
200             AnyEvent::Socket::tcp_connect $self->{host} || "localhost", $self->{port} || 2947, sub {
201 0     0     my ($fh) = @_;
202              
203 0 0         return unless $self;
204              
205 0 0         if ($fh) {
206             # unbelievable, but true: gpsd does not support command pipelining.
207             # it's an immensely shitty piece of software, actually, as it blocks
208             # randomly and for extended periods of time, has a surprisingly broken
209             # and non-configurable baud autoconfiguration system (it does stuff
210             # like switching to read-only mode when my bluetooth gps mouse temporarily
211             # loses the connection etc.) and uses rather idiotic and wasteful
212             # programming methods.
213              
214             $self->{fh} = new AnyEvent::Handle
215             fh => $fh,
216             low_delay => 1,
217             on_error => sub {
218 0           $self->event ("error");
219 0           $self->retry;
220             },
221             on_eof => sub {
222 0           $! = &Errno::EPIPE;
223 0           $self->event ("error");
224 0           $self->log ("disconnect");
225 0           $self->retry;
226             },
227             on_read => sub {
228 0 0         $_[0]{rbuf} =~ s/^([^\015\012]*)\015\012//
229             or return;
230              
231 0 0         $self->feed ($1)
232             unless $self->{replay_cb};
233             },
234 0           ;
235              
236 0           $self->send ("w");
237 0           $self->send ("o");
238 0           $self->send ("y");
239 0           $self->send ("c");
240              
241 0           $self->event ("connect");
242 0           $self->log ("connect");
243             } else {
244 0           $self->event ("error");
245             }
246 0   0       };
      0        
247              
248 0           Scalar::Util::weaken $self;
249             }
250              
251             sub drain_wbuf {
252 0     0 0   my ($self) = @_;
253              
254 0           $self->{fh}->push_write (join "", @{ $self->{command}[0] });
  0            
255             }
256              
257             sub send {
258 0     0 0   my ($self, $command, $args) = @_;
259              
260             # curse them, we simply expect that each comamnd will result in a response using
261             # the same letter
262              
263 0           push @{ $self->{command} }, [uc $command, $args];
  0            
264 0 0         $self->drain_wbuf if @{ $self->{command} } == 1;
  0            
265             }
266              
267             sub feed {
268 0     0 0   my ($self, $line) = @_;
269              
270 0           $self->{now} = AnyEvent->now;
271              
272 0 0         $self->log (raw => $line)
273             if $self->{logfh};
274              
275 0 0         unless ($line =~ /^GPSD,(.)=(.*)$/) {
276 0           $! = &Errno::EBADMSG;
277 0           $self->event ("error");
278 0           return $self->retry;
279             }
280              
281 0           my ($type, $data) = ($1, $2);
282              
283             #warn "$type=$data\n";#d#
284              
285 0           $self->{state}{$type} = [$data => $self->{now}];
286              
287 0 0         if ($type eq "O") {
    0          
    0          
288 0           my @data = split /\s+/, $data;
289              
290 0           my $fix = $self->{fix};
291              
292 0           $fix->{time} = $self->{now};
293              
294 0 0         if (@data > 3) {
295             # the gpsd time is virtually useless as it is truncated :/
296 0           for (qw(tag _time _terr lat lon alt herr verr bearing speed vspeed berr serr vserr mode)) {
297 0           $type = shift @data;
298 0 0         $fix->{$_} = $type eq "?" ? undef : $type;
299             }
300              
301 0 0         if (my $s = $self->{stretch}) {
302 0           $s = 1 / $s;
303              
304 0           $fix->{herr} *= $s; # ?
305 0           $fix->{verr} *= $s; # ?
306 0           $fix->{berr} *= $s; # ?
307 0           $fix->{serr} *= $s; # ?
308 0           $fix->{vserr} *= $s; # ?
309              
310 0           $fix->{speed} *= $s;
311 0           $fix->{vspeed} *= $s;
312             }
313              
314 0 0         $fix->{mode} = 2 if $fix->{mode} eq "?"; # arbitrary choice
315             } else {
316 0           $fix->{mode} = 1;
317             }
318              
319 0           $self->event (fix => $fix);
320              
321             } elsif ($type eq "Y") {
322 0           my (undef, @sats) = split /:/, $data;
323            
324 0           $self->{satellite_info} = [map {
325 0           my @sat = split /\s+/;
326             {
327 0           prn => $sat[0],
328             ele => $sat[1],
329             azi => $sat[2],
330             snr => $sat[3],
331             fix => $sat[4],
332             }
333             } @sats];
334              
335 0           $self->event (satellite_update => $self->{satellite_info});
336            
337             } elsif ($type eq "C") {
338 0 0         $self->{interval} = $data >= 1 ? $data * 1 : 1;
339             }
340              
341             # we (wrongly) assume that gpsd responses are always in response
342             # to an earlier command
343              
344 0 0 0       if (@{ $self->{command} } && $self->{command}[0][0] eq $type) {
  0            
345 0           shift @{ $self->{command} };
  0            
346 0 0         $self->drain_wbuf if @{ $self->{command} };
  0            
347             }
348             }
349              
350             =item ($lat, $lon) = $gps->estimate ([$max_seconds])
351              
352             This returns an estimate of the current position based on the last fix and
353             the time passed since then.
354              
355             Useful for interactive applications where you want more frequent updates,
356             but not very useful to store, as the next fix might well be totally
357             off. For example, when displaying a real-time map, you could simply call
358             C ten times a second and update the cursor or map position, but
359             you should use C to actually gather data to plot the course itself.
360              
361             If the fix is older then C<$max_seconds> (default: C<1.9> times the update
362             interval, i.e. usually C<1.9> seconds) or if no fix is available, returns
363             the empty list.
364              
365             =cut
366              
367             sub estimate {
368 0     0 1   my ($self, $max) = @_;
369              
370 0 0 0       $max ||= 1.9 * $self->{interval} unless defined $max;
371              
372 0   0       my $geo = $self->{geo_forward} ||= new Geo::Forward;
373              
374 0 0         my $fix = $self->{fix} or return;
375 0 0         $fix->{mode} >= 2 or return;
376              
377 0           my $diff = AnyEvent->time - $fix->{time};
378              
379 0 0         $diff <= $max or return;
380              
381 0 0         if ($fix->{speed} >= $self->{min_speed}) {
382 0           my ($lat, $lon) = $geo->forward ($fix->{lat}, $fix->{lon}, $fix->{bearing}, $fix->{speed} * $diff);
383 0           ($lat, $lon)
384              
385             } else {
386             # if we likely have zero speed, return the point itself
387 0           ($fix->{lat}, $fix->{lon})
388             }
389             }
390              
391             sub log {
392 0     0 0   my ($self, @arg) = @_;
393              
394 0 0         syswrite $self->{logfh}, JSON::encode_json ([AnyEvent->time, @arg]) . "\n"
395             if $self->{logfh};
396             }
397              
398             =item $gps->record_log ($path)
399              
400             If C<$path> is defined, then that file will be created or truncated and a
401             log of all (raw) packets received will be written to it. This log file can
402             later be replayed by calling C<< $gps->replay_log ($path) >>.
403              
404             If C<$path> is undefined then the log will be closed.
405              
406             =cut
407              
408             sub record_log {
409 0     0 1   my ($self, $path) = @_;
410              
411 0 0         if (defined $path) {
    0          
412 0           $self->record_log;
413              
414 0           require JSON;
415              
416 0 0         open $self->{logfh}, ">:perlio", $path
417             or Carp::croak "$path: $!";
418              
419 0           $self->log (start => $VERSION, 0, 0, { interval => $self->{interval} });
420             } elsif ($self->{logfh}) {
421 0           $self->log ("stop");
422 0           delete $self->{logfh};
423             }
424             }
425              
426             =item $gps->replay_log ($path, %options)
427              
428             Replays a log file written using C (or stops replaying when
429             C<$path> is undefined). While the log file replays, real GPS events will
430             be ignored. This comes in handy when testing.
431              
432             Please note that replaying a log will change configuration options that
433             will not be restored, so it's best not to reuse a gpsd object after a
434             replay.
435              
436             The C distribution comes with an example log
437             (F) that you can replay for testing or enjoyment
438             purposes.
439              
440             The options include:
441              
442             =over 4
443              
444             =item compress => 1
445              
446             If set to a true value (default: false), then passages without fix will be
447             replayed much faster than passages with fix. The same happens for passages
448             without much movement.
449              
450             =item stretch => $factor
451              
452             Multiplies all times by the given factor. Values < 1 make the log replay
453             faster, values > 1 slower. Note that the frequency of fixes will not be
454             increased, o stretch factors > 1 do not work well.
455              
456             A stretch factor of zero is not allowed, but if you want to replay a log
457             instantly you may speicfy a very low value (e.g. 1e-10).
458              
459             =back
460              
461             =cut
462              
463             sub replay_log {
464 0     0 1   my ($self, $path, %option) = @_;
465              
466 0 0         if (defined $path) {
467 0           $self->replay_log;
468              
469 0           require JSON;
470              
471 0 0         open my $fh, "<:perlio", $path
472             or Carp::croak "$path: $!";
473              
474 0   0       $self->{stretch} = $option{stretch} || 1;
475 0           $self->{compress} = $option{compress};
476              
477 0           $self->{imterval} /= $self->{stretch};
478              
479 0           Scalar::Util::weaken $self;
480              
481             $self->{replay_cb} = sub {
482 0     0     my $line = <$fh>;
483              
484 0 0         if (2 > length $line) {
485 0           $self->replay_log;
486             } else {
487 0           my ($time, $type, @data) = @{ JSON::decode_json ($line) };
  0            
488              
489 0           $time *= $self->{stretch};
490              
491 0 0         if ($type eq "start") {
492 0           my ($module_version, $major_version, $minor_version, $args) = @data;
493              
494 0   0       $self->{interval} = ($args->{interval} || 1) / $self->{stretch};
495             }
496              
497 0 0 0       if (
      0        
      0        
      0        
498             $type eq "start"
499             or ($self->{compress}
500             and $self->{fix} && ($self->{fix}{mode} < 2 || $self->{fix}{speed} < $self->{min_speed}))
501             ) {
502 0           $self->{replay_now} = $time;
503             }
504              
505             $self->{replay_timer} = AnyEvent->timer (after => $time - $self->{replay_now}, cb => sub {
506 0           $self->{replay_now} = $time;
507 0           $self->{command} = []; # no can do
508 0 0         $self->feed ($data[0]) if $type eq "raw";
509 0           $self->{replay_cb}();
510 0           });
511             }
512 0           };
513              
514 0           $self->{replay_cb}();
515              
516             } else {
517 0           delete $self->{stretch};
518 0           delete $self->{compress};
519 0           delete $self->{replay_timer};
520 0           delete $self->{replay_cb};
521             }
522             }
523              
524             =back
525              
526             =head1 SEE ALSO
527              
528             L.
529              
530             =head1 AUTHOR
531              
532             Marc Lehmann
533             http://home.schmorp.de/
534              
535             =cut
536              
537             1
538