ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-GPSD/GPSD.pm
Revision: 1.3
Committed: Wed Jul 2 05:21:58 2008 UTC (15 years, 10 months ago) by root
Branch: MAIN
Changes since 1.2: +7 -4 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 The passed hash references are read-only.
97
98 =item on_fix => $cb->({point})
99
100 Called regularly (usually about once/second), even when there is no
101 connection to the GPSD (so is useful to update your idea of the current
102 position). The passed hash reference must I<not> be modified in any way.
103
104 If C<mode> is C<2> or C<3>, then the C<{point}> hash contains at least the
105 following members, otherwise it is undefined which members exist. Members
106 whose values are not known are C<undef> (usually the error values, speed
107 and so on).
108
109 time when this fix was received (s)
110
111 lat latitude (S -90..90 N)
112 lon longitude (W -180..180 E)
113 alt altitude
114
115 herr estimated horizontal error (m)
116 verr estimated vertical error (m)
117
118 bearing bearing over ground (0..360)
119 berr estimated error in bearing (degrees)
120 speed speed over ground (m/s)
121 serr estimated error in speed over ground (m/s)
122 vspeed vertical velocity, positive = upwards (m/s)
123 vserr estimated error in vspeed (m/s)
124
125 mode 1 = no fix, 2 = 2d fix, 3 = 3d fix
126
127 =back
128
129 =cut
130
131 sub new {
132 my $class = shift;
133 my $self = bless {
134 @_,
135 interval => 1,
136 fix => { time => AnyEvent->now, mode => 1 },
137 }, $class;
138
139 $self->interval_timer;
140 $self->connect;
141
142 $self
143 }
144
145 sub event {
146 my $event = splice @_, 1, 1, ();
147
148 warn "event<$event,@_>\n";#d#
149 if ($event = $_[0]{"on_$event"}) {
150 &$event;
151 }
152 }
153
154 sub retry {
155 my ($self) = @_;
156
157 delete $self->{fh};
158
159 Scalar::Util::weaken $self;
160 $self->{retry_w} = AnyEvent->timer (after => 1, cb => sub {
161 delete $self->{retry_w};
162 $self->connect;
163 });
164 }
165
166 # make sure we send "no fix" updates when we lose connectivity
167 sub interval_timer {
168 my ($self) = @_;
169
170 $self->{interval_w} = AnyEvent->timer (after => $self->{interval}, cb => sub {
171 if (AnyEvent->now - $self->{fix}{time} > $self->{interval} * 1.9) {
172 $self->{fix}{mode} = 1;
173 $self->event (fix => $self->{fix});
174 }
175
176 $self->interval_timer;
177 });
178
179 Scalar::Util::weaken $self;
180 }
181
182 sub connect {
183 my ($self) = @_;
184
185 return if $self->{fh};
186
187 AnyEvent::Socket::tcp_connect $self->{host} || "localhost", $self->{port} || 2947, sub {
188 my ($fh) = @_;
189
190 return unless $self;
191
192 if ($fh) {
193 # unbelievable, but true: gpsd does not support command pipelining.
194 # it's an immensely shitty piece of software, actually, as it blocks
195 # randomly and for extended periods of time, has a surprisingly broken
196 # and non-configurable baud autoconfiguration system (it does stuff
197 # like switching to read-only mode when my bluetooth gps mouse temporarily
198 # loses the connection etc.) and uses rather idiotic and wasteful
199 # programming methods.
200
201 $self->{fh} = new AnyEvent::Handle
202 fh => $fh,
203 low_delay => 1,
204 on_error => sub {
205 $self->event ("error");
206 $self->retry;
207 },
208 on_eof => sub {
209 $! = &Errno::EPIPE;
210 $self->event ("error");
211 $self->retry;
212 },
213 on_read => sub {
214 $_[0]{rbuf} =~ s/^([^\015\012]*)\015\012//
215 or return;
216
217 $self->feed ($1);
218 },
219 ;
220
221 $self->send ("w");
222 $self->send ("o");
223 $self->send ("y");
224 $self->send ("c");
225
226 $self->event ("connect");
227 } else {
228 $self->event ("error");
229 }
230 };
231
232 Scalar::Util::weaken $self;
233 }
234
235 sub drain_wbuf {
236 my ($self) = @_;
237
238 $self->{fh}->push_write (join "", @{ $self->{command}[0] });
239 }
240
241 sub send {
242 my ($self, $command, $args) = @_;
243
244 # curse them, we simply expect that each comamnd will result in a response using
245 # the same letter
246
247 push @{ $self->{command} }, [uc $command, $args];
248 $self->drain_wbuf if @{ $self->{command} } == 1;
249 }
250
251 sub feed {
252 my ($self, $line) = @_;
253
254 $self->{now} = AnyEvent->now;
255
256 unless ($line =~ /^GPSD,(.)=(.*)$/) {
257 $! = &Errno::EBADMSG;
258 $self->event ("error");
259 return $self->retry;
260 }
261
262 my ($type, $data) = ($1, $2);
263
264 $self->{state}{$type} = [$data => $self->{now}];
265
266 if ($type eq "O") {
267 my @data = split /\s+/, $data;
268
269 my $fix = $self->{fix};
270
271 $fix->{time} = $self->{now};
272
273 if (@data > 3) {
274 # the gpsd time is virtually useless as it is truncated :/
275 for (qw(tag _time _terr lat lon alt herr verr bearing speed vspeed berr serr vserr mode)) {
276 $type = shift @data;
277 $fix->{$_} = $type eq "?" ? undef : $type;
278 }
279
280 $fix->{mode} = 2 if $fix->{mode} eq "?"; # arbitrary choice
281 } else {
282 $fix->{mode} = 1;
283 }
284
285 $self->event (fix => $fix);
286
287 } elsif ($type eq "Y") {
288 my (undef, @sats) = split /:/, $data;
289
290 $self->{satellite_info} = [map {
291 my @sat = split /\s+/;
292 {
293 prn => $sat[0],
294 ele => $sat[1],
295 azi => $sat[2],
296 snr => $sat[3],
297 fix => $sat[4],
298 }
299 } @sats];
300
301 $self->event (satellite_update => $self->{satellite_info});
302
303 } elsif ($type eq "C") {
304 $self->{interval} = $data >= 1 ? $data * 1 : 1;
305 }
306
307 # we (wrongly) assume that gpsd responses are always in response
308 # to an earlier command
309
310 if (@{ $self->{command} } && $self->{command}[0][0] eq $type) {
311 shift @{ $self->{command} };
312 $self->drain_wbuf if @{ $self->{command} };
313 }
314 }
315
316 =item ($lat, $lon) = $gps->estimate ([$max_seconds])
317
318 This returns an estimate of the current position based on the last fix and
319 the time passed since then. Useful for interactive applications where you
320 want more frequent updates, but not very useful to store, as the next fix
321 might well be totally off.
322
323 If the fix is older then C<$max_seconds> (default: C<1.9> times the update
324 interval, i.e. usually C<1.9> seconds) or if no fix is available, returns
325 the empty list.
326
327 =cut
328
329 sub estimate {
330 my ($self, $max) = @_;
331
332 $max ||= 1.9 * $self->{interval} unless defined $max;
333
334 my $geo = $self->{geo_forward} ||= new Geo::Forward;
335
336 my $fix = $self->{fix} or return;
337 $fix->{mode} >= 2 or return;
338
339 my $diff = AnyEvent->time - $fix->{time};
340
341 $diff <= $max or return;
342
343 if ($fix->{speed} > $fix->{serr}) {
344 my ($lat, $lon) = $geo->forward ($fix->{lat}, $fix->{lon}, $fix->{bearing}, $fix->{speed} * $diff);
345 ($lat, $lon)
346
347 } else {
348 # if we likely have zero speed, return the point itself
349 ($fix->{lat}, $fix->{lon})
350 }
351 }
352
353 =back
354
355 =head1 SEE ALSO
356
357 L<AnyEvent>.
358
359 =head1 AUTHOR
360
361 Marc Lehmann <schmorp@schmorp.de>
362 http://home.schmorp.de/
363
364 =cut
365
366 1
367