File Coverage

blib/lib/GPS/Garmin/Handler.pm
Criterion Covered Total %
statement 29 366 7.9
branch 1 128 0.7
condition 1 19 5.2
subroutine 8 54 14.8
pod 0 29 0.0
total 39 596 6.5


line stmt bran cond sub pod time code
1             # Copyright (c) 1999-2000 João Pedro Gonçalves .
2             #All rights reserved. This program is free software;
3             #you can redistribute it and/or modify it under the same terms as Perl itself.
4              
5             package GPS::Garmin::Handler;
6              
7 2     2   9 use strict;
  2         3  
  2         65  
8 2     2   9 use vars qw($VERSION @ISA);
  2         2  
  2         113  
9              
10 2     2   3392 use GPS::Garmin::Constant ':all';
  2         17  
  2         2869  
11              
12             $VERSION = sprintf("%d.%02d", q$Revision: 1.16 $ =~ /(\d+)\.(\d+)/);
13              
14             #$|++; # XXX should not be here...
15              
16             # Don't pull in Math::Trig for just these two functions
17             sub pi () { 4 * atan2(1, 1) } # 3.141592653...
18 0     0 0 0 sub rad2deg { ($_[0]*180)/pi }
19              
20             sub new {
21 2     2 0 3 my($class, $p) = @_;
22 2         6 my $self = bless { p => $p }, $class;
23 2 50 33     2 if (eval { require Scalar::Util; Scalar::Util->import(qw(weaken)); 1 } ||
  2         8  
  2         78  
  2         8  
24 0         0 eval { require WeakRef; WeakRef->import(qw(weaken)); 1 }) {
  0         0  
  0         0  
25 2         15 weaken($self->{p}); # break self-reference
26             }
27 2         8 $self;
28             }
29              
30 0     0 0   sub p { $_[0]->{p} }
31              
32             # The Garmin documentation says 1.0e+25, but take floating point inaccuracies
33             # into account.
34 2     2   18 use constant UNDEF_FLOAT => 9.9e+24;
  2         3  
  2         6500  
35              
36             #Fail
37             sub Nak_byte {
38 0     0 0   shift->p->read_packet;
39 0           GRMN_NAK
40             }
41              
42             # - Ack byte - the GPS ACKnowledged, read the packet and move next.
43             sub Ack_byte {
44 0     0 0   shift->p->read_packet;
45 0           GRMN_ACK
46             }
47              
48             # XXX use result_as_hash here too? But implementation of
49             # GPS::Garmin::get_product_id would be more complex...
50             sub Product_data {
51 0     0 0   my ($data) = shift->p->read_packet;
52 0           unpack("ssZ*",$data);
53             }
54              
55             ######################################################################
56             # Waypoints
57              
58             sub Wpt_data_D103 {
59 0     0 0   my $self = shift;
60 0           my $p = $self->p;
61 0           $p->{records}--;
62 0           my ($data) = $p->read_packet;
63              
64 0           my $ident = substr($data,0,6,'');
65 0           my $comment = substr($data,12,40,'');
66 0           my($lat,$lon) = unpack("ll",$data);
67 0           $lat = $p->semicirc_deg($lat);
68 0           $lon = $p->semicirc_deg($lon);
69 0           $p->send_packet(GRMN_ACK);
70              
71 0 0         if ($p->{records} == 0) { $p->get_reply }
  0            
72              
73 0 0         if ($p->{return_as_hash}) {
74 0           (ident => $ident,
75             lat => $lat,
76             lon => $lon,
77             comment => $comment,
78             );
79             } else {
80 0           ($ident, $lat, $lon, $comment);
81             }
82             }
83              
84             sub Wpt_data_D108 {
85 0     0 0   my($self) = @_;
86 0           my $p = $self->p;
87 0           $p->{records}--;
88 0           my ($data) = $p->read_packet;
89              
90 0           my %res;
91 0           @res{qw{wpt_class color dspl attr}} = unpack("C4", substr($data,0,4,''));
92 0           $res{smbl} = unpack("s", substr($data,0,2,''));
93 0           $res{subclass} = substr($data,0,18,''); # XXX chr(255)x18 == undef?
94 0           my($lt,$ln) = unpack("ll", substr($data,0,4*2,''));
95 0           $res{lat} = $p->semicirc_deg($lt);
96 0           $res{lon} = $p->semicirc_deg($ln);
97 0           @res{qw{alt dpth dist}} = unpack("f3", substr($data,0,4*3,''));
98 0           for (qw(alt dpth dist)) {
99 0 0         if ($res{$_} >= UNDEF_FLOAT) {
100 0           $res{$_} = undef;
101             }
102             }
103 0           $res{state} = unpack("a2", substr($data,0,2,''));
104 0           $res{cc} = unpack("a2", substr($data,0,2,''));
105 0           @res{qw{ident comment facility city addr cross_road}} = split /\0/, $data;
106              
107 0           $p->send_packet(GRMN_ACK);
108              
109 0 0         if ($p->{records} == 0) { $p->get_reply }
  0            
110              
111 0 0         if ($p->{return_as_hash}) {
112 0           %res;
113             } else {
114 0           @res{qw{ident lat lon comment}};
115             }
116             }
117              
118             sub _Wpt_data_D109_and_better {
119 0     0     my($self) = @_;
120 0           my $p = $self->p;
121 0           $p->{records}--;
122 0           my ($data) = $p->read_packet;
123              
124 0           my %res;
125 0           @res{qw{dtyp wpt_class dspl_color attr}} = unpack("C4", substr($data,0,4,''));
126 0           $res{color} = $res{dspl_color} & 0x1f;
127 0           $res{dspl} = ($res{dspl_color} >> 5) & 0x3;
128 0           $res{smbl} = unpack("s", substr($data,0,2,''));
129 0           $res{subclass} = substr($data,0,18,''); # XXX chr(255)x18 == undef?
130 0           my($lt,$ln) = unpack("ll", substr($data,0,4*2,''));
131 0           $res{lat} = $p->semicirc_deg($lt);
132 0           $res{lon} = $p->semicirc_deg($ln);
133 0           @res{qw{alt dpth dist}} = unpack("f3", substr($data,0,4*3,''));
134 0           for (qw(alt dpth dist)) {
135 0 0         if ($res{$_} >= UNDEF_FLOAT) {
136 0           $res{$_} = undef;
137             }
138             }
139 0           $res{state} = unpack("a2", substr($data,0,2,''));
140 0           $res{cc} = unpack("a2", substr($data,0,2,''));
141 0           $res{ete} = unpack("l", substr($data,0,4,''));
142 0 0         if ($res{attr} == 0x80) { # D110
143 0           $res{temp} = unpack("f", substr($data,0,4,''));
144 0           $res{time} = unpack("l", substr($data,0,4,''));
145 0           $res{wpt_cat} = unpack("s", substr($data,0,2,''));
146             }
147 0           @res{qw{ident comment facility city addr cross_road}} = split /\0/, $data;
148              
149 0           $p->send_packet(GRMN_ACK);
150              
151 0 0         if ($p->{records} == 0) { $p->get_reply }
  0            
152              
153 0 0         if ($p->{return_as_hash}) {
154 0           %res;
155             } else {
156 0           @res{qw{ident lat lon comment}};
157             }
158             }
159              
160 0     0 0   sub Wpt_data_D109 { shift->_Wpt_data_D109_and_better(@_) }
161 0     0 0   sub Wpt_data_D110 { shift->_Wpt_data_D109_and_better(@_) }
162              
163             sub pack_Wpt_data_D108 {
164 0     0 0   my $self = shift;
165 0           my $d = shift;
166 0           my %d = %$d;
167 0 0         $d{wpt_class} = 0 unless defined $d{wpt_class};
168 0 0         $d{color} = 255 unless defined $d{color};
169 0 0         $d{dspl} = 0 unless defined $d{dspl};
170 0 0         $d{attr} = 0x60 unless defined $d{attr};
171 0 0         $d{smbl} = 8246 unless defined $d{smbl};
172 0           foreach my $key (qw(alt dpth dist)) {
173 0 0         $d{$key} = 1.0e25 unless defined $d{$key};
174             }
175 0           foreach my $key (qw(state cc)) {
176 0 0         $d{$key} = " " unless defined $d{$key};
177             }
178 0           foreach my $key (qw(ident comment facility city addr cross_road)) {
179 0 0         $d{$key} = "" unless defined $d{$key};
180             }
181 0 0         if ($d{ident} eq '') {
182 0           die "ident not defined";
183             }
184 0 0 0       die "lat or lon not defined" if !defined $d{lat} || !defined $d{lon};
185 0           my $s = pack("C4s", @d{qw{wpt_class color dspl attr smbl}});
186 0           $s .= chr(255)x18; # subclass
187 0           $s .= pack("ll", $self->p->deg_semicirc($d{lat}), $self->p->deg_semicirc($d{lon}));
188 0           $s .= pack("f3", @d{qw{alt dpth dist}});
189 0           $s .= pack("A2A2", @d{qw{state cc}});
190 0           $s .= join("\0", @d{qw{ident comment facility city addr cross_road}});
191 0           $s;
192             }
193              
194             sub _pack_Wpt_data_D109_and_better {
195 0     0     my $self = shift;
196 0           my $d = shift;
197 0           my %d = %$d;
198 0 0         die "dtyp not set" if !exists $d{dtyp};
199 0 0         $d{wpt_class} = 0 unless defined $d{wpt_class};
200 0 0         $d{color} = 255 unless defined $d{color};
201 0 0         $d{dspl} = 0 unless defined $d{dspl};
202 0           $d{dspl_color} = ($d{color} & 0x1f) | (($d{dspl} & 0x3) << 5);
203 0 0         $d{smbl} = 8246 unless defined $d{smbl};
204 0           foreach my $key (qw(alt dpth dist)) {
205 0 0         $d{$key} = 1.0e25 unless defined $d{$key};
206             }
207 0           foreach my $key (qw(state cc)) {
208 0 0         $d{$key} = " " unless defined $d{$key};
209             }
210 0 0         $d{ete} = 0xffffffff if !defined $d{ete};
211 0 0         if ($d{datatype} eq 'D110') {
212 0 0         $d{temp} = 1.0e25 unless defined $d{temp};
213 0 0         $d{time} = 0xFFFFFFFF unless defined $d{time};
214 0 0         $d{wpt_cat} = 0 unless defined $d{wpt_cat};
215             }
216 0           foreach my $key (qw(ident comment facility city addr cross_road)) {
217 0 0         $d{$key} = "" unless defined $d{$key};
218             }
219 0 0         if ($d{ident} eq '') {
220 0           die "ident not defined";
221             }
222 0 0 0       die "lat or lon not defined" if !defined $d{lat} || !defined $d{lon};
223 0           my $s = pack("C4s", @d{qw{dtyp wpt_class dspl_color attr smbl}});
224 0           $s .= chr(255)x18; # subclass
225 0           $s .= pack("ll", $self->p->deg_semicirc($d{lat}), $self->p->deg_semicirc($d{lon}));
226 0           $s .= pack("f3", @d{qw{alt dpth dist}});
227 0           $s .= pack("A2A2", @d{qw{state cc}});
228 0           $s .= pack("l", $d{ete});
229 0 0         if ($d{datatype} eq 'D110') {
230 0           $s .= pack("f", $d{temp});
231 0           $s .= pack("l", $d{time});
232 0           $s .= pack("s", $d{wpt_cat});
233             }
234 0           $s .= join("\0", @d{qw{ident comment facility city addr cross_road}});
235 0           $s;
236             }
237              
238             sub pack_Wpt_data_D109 {
239 0     0 0   my($self, $d) = @_;
240 0           my %d = %$d;
241 0           $d{datatype} = "D109";
242 0           $d{dtyp} = 1; # 0x1 for D109
243 0           $d{attr} = 0x70;
244 0           $self->_pack_Wpt_data_D109_and_better(\%d);
245             }
246              
247             sub pack_Wpt_data_D110 {
248 0     0 0   my($self, $d) = @_;
249 0           my %d = %$d;
250 0           $d{datatype} = "D110";
251 0           $d{dtyp} = 1; # 0x1 for D110
252 0           $d{attr} = 0x80;
253 0           $self->_pack_Wpt_data_D109_and_better(\%d);
254             }
255              
256             ######################################################################
257             # Routes
258              
259             sub Rte_hdr {
260 0     0 0   my $self = shift;
261 0           $self->p->{records}--;
262 0           my ($data) = $self->p->read_packet;
263              
264 0           my %res;
265 0           $res{nmbr} = unpack("C", substr($data,0,1,''));
266 0           $res{cmnt} = unpack("Z*", $data);
267              
268 0           $self->p->send_packet(GRMN_ACK);
269              
270 0 0         if($self->p->{records} == 0) { $self->p->get_reply; }
  0            
271 0           return %res;
272             }
273              
274             sub pack_Rte_hdr {
275 0     0 0   my $self = shift;
276 0           my %d = %{$_[0]};
  0            
277 0 0         die "Please specify route number" if !defined $d{nmbr};
278 0 0         $d{cmnt} = "" if !defined $d{cmnt};
279             # D201
280 0           my $s = pack("C", $d{nmbr});
281 0           $s .= pack("a20", $d{cmnt});
282 0           $s;
283             }
284              
285             sub Rte_wpt_data {
286 0     0 0   my $self = shift;
287 0           $self->Wpt_data;
288             }
289              
290             sub pack_Rte_wpt_data {
291 0     0 0   my $self = shift;
292 0           $self->pack_Wpt_data(@_);
293             }
294              
295             sub Rte_link_data {
296 0     0 0   my $self = shift;
297 0           my $p = $self->p;
298 0           $p->{records}--;
299 0           my ($data) = $p->read_packet;
300              
301 0           my %res;
302 0           $res{class} = unpack("s", substr($data,0,2,''));
303 0           $res{subclass} = unpack("a18", substr($data,0,18,''));
304 0           $res{ident} = $data;
305              
306 0           $p->send_packet(GRMN_ACK);
307              
308 0 0         if($p->{records} == 0) { $p->get_reply; }
  0            
309 0           return %res;
310             }
311              
312             sub pack_Rte_link_data {
313 0     0 0   my $self = shift;
314 0   0       my $d = shift || {};
315 0           my %d = %$d;
316 0 0         $d{class} = 0 unless defined $d{class};
317 0 0         $d{subclass} = ("\0"x6).("\xff"x12) unless defined $d{subclass};
318 0 0         $d{ident} = "" unless defined $d{ident};
319             # D210
320 0           my $s = pack("s", $d{class});
321 0           $s .= pack("a18", $d{subclass});
322 0 0         $s .= substr($d{ident},0,50)."\0" if $d{ident} ne "";
323 0           $s;
324             }
325              
326             ######################################################################
327             # Almanac
328              
329             # XXX use return_as_hash
330             sub Almanac_data {
331 0     0 0   my $self = shift;
332 0           $self->p->{records}--;
333 0           my ($data) = $self->p->read_packet;
334 0           my (@ident,@comm,$lt,$ln);
335              
336             #D501 Almanac Datatype
337 0           my($wn,$toa,$af0,$af1,$e,$sqrta,$m0,$w,$omg0,$odot,$i,$htlh) =
338             unpack('sf10c',$data);
339              
340 0           $self->p->send_packet(GRMN_ACK);
341 0 0         if($self->p->{records} == 0) { $self->p->get_reply; }
  0            
342 0           return($wn,$toa,$af0,$af1,$e,$sqrta,$m0,$w,$omg0,$odot,$i,$htlh);
343             }
344              
345             ######################################################################
346             # Tracks
347              
348             sub Trk_hdr_D310 {
349 0     0 0   my $self = shift;
350 0           my $p = $self->p;
351 0           $p->{records}--;
352 0           my ($data) = $p->read_packet;
353              
354 0           my %res;
355 0           $res{dspl} = unpack("c", substr($data,0,1));
356 0           $res{color} = unpack("C", substr($data,1,1));
357 0           $res{trk_ident} = unpack("Z*", substr($data,2));
358              
359 0           $p->send_packet(GRMN_ACK);
360 0 0         if ($p->{records} == 0) { $p->get_reply }
  0            
361 0           return %res;
362             }
363              
364             sub Trk_data_D300 {
365 0     0 0   my $self = shift;
366 0           my $p = $self->p;
367 0           $p->{records}--;
368 0           my ($data) = $p->read_packet;
369 0           my (@ident,@comm,$lt,$ln);
370              
371             #D300 Track Point Datatype
372 0           my ($lat,$lon,$time,$is_first) = unpack('llLb',$data);
373 0           $lat = $p->semicirc_deg($lat);
374 0           $lon = $p->semicirc_deg($lon);
375 0           $time += GRMN_UTC_DIFF;
376              
377 0           $p->send_packet(GRMN_ACK);
378 0 0         if($p->{records} == 0) { $p->get_reply; }
  0            
379 0 0         if ($p->{return_as_hash}) {
380 0           (lat => $lat,
381             lon => $lon,
382             time => $time,
383             );
384             } else {
385 0           ($lat, $lon, $time);
386             }
387             }
388              
389             sub Trk_data_D301 {
390 0     0 0   my $self = shift;
391 0           my $p = $self->p;
392 0           $p->{records}--;
393 0           my ($data) = $p->read_packet;
394 0           my (@ident,@comm,$lt,$ln);
395              
396             # D301 Track Point Datatype
397 0           my ($lat,$lon,$time,$alt,$dpth,$new_trk) = unpack('llLffb',$data);
398 0           $lat = $p->semicirc_deg($lat);
399 0           $lon = $p->semicirc_deg($lon);
400 0 0         if ($time == 0xffffffff) { # XXX check
401 0           undef $time;
402             } else {
403 0           $time += GRMN_UTC_DIFF;
404             }
405 0 0         if ($dpth >= UNDEF_FLOAT) { $dpth = undef }
  0            
406 0 0         if ($alt >= UNDEF_FLOAT) { $alt = undef }
  0            
407              
408 0           $p->send_packet(GRMN_ACK);
409 0 0         if ($p->{records} == 0) { $p->get_reply }
  0            
410              
411 0 0         if ($p->{return_as_hash}) {
412 0           (lat => $lat,
413             lon => $lon,
414             time => $time,
415             alt => $alt,
416             dpth => $dpth,
417             new_trk => $new_trk,
418             );
419             } else {
420 0           ($lat, $lon, $time, $alt, $dpth, $new_trk);
421             }
422             }
423              
424             sub pack_Trk_data_D301 {
425 0     0 0   my $self = shift;
426 0           my $d = shift;
427 0           my %d = %$d;
428 0           foreach my $key (qw(alt dpth)) {
429 0 0         $d{$key} = 1.0e25 unless defined $d{$key};
430             }
431 0 0         $d{first} = 0 unless defined $d{first};
432 0 0         $d{time} = time + GRMN_UTC_DIFF unless defined $d{time};
433 0 0 0       die "lat or lon not defined" if !defined $d{lat} || !defined $d{lon};
434 0           my $s = pack("ll", $self->p->deg_semicirc($d{lat}), $self->p->deg_semicirc($d{lon}));
435 0           $s .= pack('Lffb', $d{time}, $d{alt}, $d{dpth}, $d{first});
436 0           $s;
437             }
438              
439             sub pack_Trk_hdr_D310 {
440 0     0 0   my $self = shift;
441 0   0       my $d = shift || {};
442 0           my %d = %$d;
443 0 0         $d{dspl} = 0 unless defined $d{dspl};
444 0 0         $d{color} = 255 unless defined $d{color};
445 0 0         if (!defined $d{ident}) {
446 0           die "ident is required";
447             }
448             # D310
449 0           my $s = pack("cC", $d{dspl}, $d{color});
450 0           $s .= $d{ident}."\0";
451 0           $s;
452             }
453              
454             ######################################################################
455              
456             sub Xfer_cmplt {
457 0     0 0   my $self = shift;
458 0           delete $self->p->{records};
459 0           delete $self->p->{cur_pid};
460 0           delete $self->p->{cur_request};
461 0           return(1);
462             }
463              
464             #Position from the GPS
465             sub Position_data {
466 0     0 0   my $self = shift;
467 0           my ($lat,$lon,$ltcord,$lncord);
468              
469 0           my ($data) = $self->p->read_packet;
470              
471              
472 0           $lat = substr($data,0,8);
473 0           $lon = substr($data,8,8);
474              
475              
476 0           $lat = rad2deg(unpack("d*",$lat));
477              
478 0           $lon = rad2deg(unpack("d*",$lon));
479              
480 0 0         $ltcord = "N";$ltcord = "S" if $lat < 0;
  0            
481 0 0         $lncord = "E";$lncord = "W" if $lon < 0;
  0            
482 0           $lat = abs($lat);$lon = abs($lon);
  0            
483 0           $lat = int($lat)+($lat - int($lat))*60/100;
484 0           $lon = int($lon)+($lon - int($lon))*60/100;
485              
486 0           $self->p->send_packet(GRMN_ACK);
487 0           return($ltcord,$lat,$lncord,$lon);
488              
489             }
490              
491              
492             sub Date_time_data {
493 0     0 0   my $self = shift;
494 0           my(@date);
495              
496 0           my $data = $self->p->safe_read;
497              
498 0           for(my $i=0;$i < 8;$i++) {
499 0           $data = $self->p->safe_read;
500              
501 0 0 0       if ($i == 2 || $i == 3) {
502 0           $date[2] .= $data;next;
  0            
503             }
504              
505 0           $date[$i] = ord $data;
506 0 0         $date[$i] =~ s/(\d)/0$1/ if length($date[$i]) == 1;
507             }
508              
509 0           $date[2] = unpack("s*",$date[2]);
510 0           return ($date[7],$date[6],$date[4],$date[1],$date[0],$date[2],1);
511             }
512              
513             sub Records {
514              
515 0     0 0   my ($self,$command) = @_;
516 0           my ($numrec,$buf,$len);
517              
518 0           $buf = $self->p->safe_read;
519              
520 0           for (my $i=0;$i<2;$i++) {
521 0           $self->p->usleep(5);
522 0           $numrec .= $self->p->safe_read;
523             }
524              
525 0           $buf = $self->p->_read(3);
526 0           $numrec = unpack("S*",$numrec);
527              
528 0           $self->p->send_packet(GRMN_ACK);
529 0           $self->p->{records} = $numrec;
530 0           return $numrec;
531             }
532              
533             ######################################################################
534              
535             package GPS::Garmin::Handler::Generic;
536 2     2   17 use vars qw(@ISA);
  2         3  
  2         277  
537             @ISA = qw(GPS::Garmin::Handler);
538              
539 0     0     sub Wpt_data { shift->Wpt_data_D103(@_) }
540 0     0     sub Trk_data { shift->Trk_data_D300(@_) }
541 0     0     sub pack_Wpt_data { shift->pack_Wpt_data_D103(@_) }
542 0     0     sub pack_Trk_data { shift->pack_Trk_data_D300(@_) }
543              
544             ######################################################################
545              
546             package GPS::Garmin::Handler::EtrexVenture;
547 2     2   8 use vars qw(@ISA);
  2         19  
  2         269  
548             @ISA = qw(GPS::Garmin::Handler);
549              
550 0     0     sub Wpt_data { shift->Wpt_data_D108(@_) }
551 0     0     sub Trk_data { shift->Trk_data_D301(@_) }
552 0     0     sub Trk_hdr { shift->Trk_hdr_D310(@_) }
553 0     0     sub pack_Wpt_data { shift->pack_Wpt_data_D108(@_) }
554 0     0     sub pack_Trk_data { shift->pack_Trk_data_D301(@_) }
555 0     0     sub pack_Trk_hdr { shift->pack_Trk_hdr_D310(@_) }
556              
557             ######################################################################
558              
559             package GPS::Garmin::Handler::EtrexVistaHCx;
560 2     2   7 use vars qw(@ISA);
  2         3  
  2         245  
561             @ISA = qw(GPS::Garmin::Handler);
562              
563 0     0     sub Wpt_data { shift->Wpt_data_D110(@_) }
564 0     0     sub Trk_data { shift->Trk_data_D301(@_) }
565 0     0     sub Trk_hdr { shift->Trk_hdr_D310(@_) }
566 0     0     sub pack_Wpt_data { shift->pack_Wpt_data_D110(@_) }
567 0     0     sub pack_Trk_data { shift->pack_Trk_data_D301(@_) }
568 0     0     sub pack_Trk_hdr { shift->pack_Trk_hdr_D310(@_) }
569              
570             1;
571              
572              
573             __END__