ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-GPSD/GPSD.pm
Revision: 1.2
Committed: Wed Jul 2 05:17:37 2008 UTC (15 years, 10 months ago) by root
Branch: MAIN
Changes since 1.1: +43 -9 lines
Log Message:
*** empty log message ***

File Contents

# Content
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<AnyEvent> 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 use strict;
28 no warnings;
29
30 use Carp ();
31 use Errno ();
32 use Scalar::Util ();
33 use Geo::Forward ();
34
35 use AnyEvent ();
36 use AnyEvent::Util ();
37 use AnyEvent::Socket ();
38 use AnyEvent::Handle ();
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<localhost:2947> 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<locahost>.
57
58 =item port => $port
59
60 The port to connect to, default is C<2947>.
61
62 =item on_error => $cb->($gps)
63
64 Called on every connection or protocol failure, reason is in C<$!>
65 (protocl errors are signalled via EBADMSG). Can be used to bail out if you
66 are not interested in retries.
67
68 =item on_connect => $cb->($gps)
69
70 Nornormally used: Called on every successful connection establish.
71
72 =item on_response => $cb->($gps, $type, $data, $time)
73
74 Not normally used: Called on every response received from GPSD. C<$type>
75 is the single letter type and C<$data> is the data portion, if
76 any. C<$time> is the timestamp that this message was received at.
77
78 =item on_satellite_info => $cb->($gps, {satellite-info}...)
79
80 Called each time the satellite info changes, also on first connect. Each
81 C<satellite-info> hash contains at least the following members (mnemonic:
82 all keys have three letters):
83
84 C<prn> holds the satellite PRN (1..32 GPS, anything higher is
85 wASS/EGNOS/MCAS etc, see L<GPS::PRN>).
86
87 C<ele>, C<azi> contain the elevation (0..90) and azimuth (0..359) of the satellite.
88
89 C<snr> contains the signal strength in decibals (28+ is usually the
90 minimum value for a good fix).
91
92 C<fix> contains either C<1> to indicate that this satellite was used for
93 the last position fix, C<0> otherwise. EGNOS/WAAS etc. satellites will
94 always show as C<0>, even if their correction info was used.
95
96 =item on_fix => $cb->({point})
97
98 Called regularly (usually about once/second), even when there is no
99 connection to the GPSD (so is useful to update your idea of the current
100 position). The passed hash reference must I<not> be modified in any way.
101
102 If C<mode> is C<2> or C<3>, then the C<{point}> hash contains at least the
103 following members, otherwise it is undefined which members exist. Members
104 whose values are not known are C<undef> (usually the error values, speed
105 and so on).
106
107 time when this fix was received (s)
108
109 lat latitude (S -90..90 N)
110 lon longitude (W -180..180 E)
111 alt altitude
112
113 herr estimated horizontal error (m)
114 verr estimated vertical error (m)
115
116 bearing bearing over ground (0..360)
117 berr estimated error in bearing (degrees)
118 speed speed over ground (m/s)
119 serr estimated error in speed over ground (m/s)
120 vspeed vertical velocity, positive = upwards (m/s)
121 vserr estimated error in vspeed (m/s)
122
123 mode 1 = no fix, 2 = 2d fix, 3 = 3d fix
124
125 =back
126
127 =cut
128
129 sub new {
130 my $class = shift;
131 my $self = bless {
132 @_,
133 interval => 1,
134 fix => { time => AnyEvent->now, mode => 1 },
135 }, $class;
136
137 $self->interval_timer;
138 $self->connect;
139
140 $self
141 }
142
143 sub event {
144 my $event = splice @_, 1, 1, ();
145
146 warn "event<$event,@_>\n";#d#
147 if ($event = $_[0]{"on_$event"}) {
148 &$event;
149 }
150 }
151
152 sub retry {
153 my ($self) = @_;
154
155 delete $self->{fh};
156
157 Scalar::Util::weaken $self;
158 $self->{retry_w} = AnyEvent->timer (after => 1, cb => sub {
159 delete $self->{retry_w};
160 $self->connect;
161 });
162 }
163
164 # make sure we send "no fix" updates when we lose connectivity
165 sub interval_timer {
166 my ($self) = @_;
167
168 $self->{interval_w} = AnyEvent->timer (after => $self->{interval}, cb => sub {
169 if (AnyEvent->now - $self->{fix}{time} > $self->{interval} * 1.9) {
170 $self->{fix}{mode} = 1;
171 $self->event (fix => $self->{fix});
172 }
173
174 $self->interval_timer;
175 });
176
177 Scalar::Util::weaken $self;
178 }
179
180 sub connect {
181 my ($self) = @_;
182
183 return if $self->{fh};
184
185 AnyEvent::Socket::tcp_connect $self->{host} || "localhost", $self->{port} || 2947, sub {
186 my ($fh) = @_;
187
188 return unless $self;
189
190 if ($fh) {
191 # unbelievable, but true: gpsd does not support command pipelining.
192 # it's an immensely shitty piece of software, actually, as it blocks
193 # randomly and for extended periods of time, has a surprisingly broken
194 # and non-configurable baud autoconfiguration system (it does stuff
195 # like switching to read-only mode when my bluetooth gps mouse temporarily
196 # loses the connection etc.) and uses rather idiotic and wasteful
197 # programming methods.
198
199 $self->{fh} = new AnyEvent::Handle
200 fh => $fh,
201 low_delay => 1,
202 on_error => sub {
203 $self->event ("error");
204 $self->retry;
205 },
206 on_eof => sub {
207 $! = &Errno::EPIPE;
208 $self->event ("error");
209 $self->retry;
210 },
211 on_read => sub {
212 $_[0]{rbuf} =~ s/^([^\015\012]*)\015\012//
213 or return;
214
215 $self->feed ($1);
216 },
217 ;
218
219 $self->send ("w");
220 $self->send ("o");
221 $self->send ("y");
222 $self->send ("c");
223
224 $self->event ("connect");
225 } else {
226 $self->event ("error");
227 }
228 };
229
230 Scalar::Util::weaken $self;
231 }
232
233 sub drain_wbuf {
234 my ($self) = @_;
235
236 $self->{fh}->push_write (join "", @{ $self->{command}[0] });
237 }
238
239 sub send {
240 my ($self, $command, $args) = @_;
241
242 # curse them, we simply expect that each comamnd will result in a response using
243 # the same letter
244
245 push @{ $self->{command} }, [uc $command, $args];
246 $self->drain_wbuf if @{ $self->{command} } == 1;
247 }
248
249 sub feed {
250 my ($self, $line) = @_;
251
252 $self->{now} = AnyEvent->now;
253
254 unless ($line =~ /^GPSD,(.)=(.*)$/) {
255 $! = &Errno::EBADMSG;
256 $self->event ("error");
257 return $self->retry;
258 }
259
260 my ($type, $data) = ($1, $2);
261
262 $self->{state}{$type} = [$data => $self->{now}];
263
264 if ($type eq "O") {
265 my @data = split /\s+/, $data;
266
267 my $fix = $self->{fix};
268
269 $fix->{time} = $self->{now};
270
271 if (@data > 3) {
272 # the gpsd time is virtually useless as it is truncated :/
273 for (qw(tag _time _terr lat lon alt herr verr bearing speed vspeed berr serr vserr mode)) {
274 $type = shift @data;
275 $fix->{$_} = $type eq "?" ? undef : $type;
276 }
277
278 $fix->{mode} = 2 if $fix->{mode} eq "?"; # arbitrary choice
279 } else {
280 $fix->{mode} = 1;
281 }
282
283 $self->event (fix => $fix);
284
285 } elsif ($type eq "Y") {
286 my (undef, @sats) = split /:/, $data;
287
288 $self->{satellite_info} = [map {
289 my @sat = split /\s+/;
290 {
291 prn => $sat[0],
292 ele => $sat[1],
293 azi => $sat[2],
294 snr => $sat[3],
295 fix => $sat[4],
296 }
297 } @sats];
298
299 $self->event (satellite_update => $self->{satellite_info});
300
301 } elsif ($type eq "C") {
302 $self->{interval} = $data >= 1 ? $data * 1 : 1;
303 }
304
305 # we (wrongly) assume that gpsd responses are always in response
306 # to an earlier command
307
308 if (@{ $self->{command} } && $self->{command}[0][0] eq $type) {
309 shift @{ $self->{command} };
310 $self->drain_wbuf if @{ $self->{command} };
311 }
312 }
313
314 =item ($lat, $lon) = $gps->estimate ([$max_seconds])
315
316 This returns an estimate of the current position based on the last fix and
317 the time passed since then. Useful for interactive applications where you
318 want more frequent updates, but not very useful to store, as the next fix
319 might well be totally off.
320
321 If the fix is older then C<$max_seconds> (default: C<1.9>) or if no fix is
322 available, returns the empty list.
323
324 =cut
325
326 sub estimate {
327 my ($self, $max) = @_;
328
329 $max ||= 1.9 unless defined $max;
330
331 my $geo = $self->{geo_forward} ||= new Geo::Forward;
332
333 my $fix = $self->{fix} or return;
334 $fix->{mode} >= 2 or return;
335
336 my $diff = AnyEvent->time - $fix->{time};
337
338 $diff <= $max or return;
339
340 if ($fix->{speed} > $fix->{serr}) {
341 my ($lat, $lon) = $geo->forward ($fix->{lat}, $fix->{lon}, $fix->{bearing}, $fix->{speed} * $diff);
342 ($lat, $lon)
343
344 } else {
345 # if we likely have zero speed, return the point itself
346 ($fix->{lat}, $fix->{lon})
347 }
348 }
349
350 =back
351
352 =head1 SEE ALSO
353
354 L<AnyEvent>.
355
356 =head1 AUTHOR
357
358 Marc Lehmann <schmorp@schmorp.de>
359 http://home.schmorp.de/
360
361 =cut
362
363 1
364