File Coverage

blib/lib/GPS/Garmin.pm
Criterion Covered Total %
statement 34 213 15.9
branch 2 82 2.4
condition 2 32 6.2
subroutine 10 37 27.0
pod 0 27 0.0
total 48 391 12.2


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;
6              
7 2     2   1469 use GPS::Base ();
  2         4  
  2         33  
8 2     2   649 use GPS::Serial ();
  2         14  
  2         46  
9 2     2   1148 use GPS::Garmin::Handler ();
  2         5  
  2         49  
10 2     2   12 use GPS::Garmin::Constant ':all';
  2         2  
  2         2582  
11              
12 2     2   13 use strict;
  2         3  
  2         69  
13 2     2   10 use vars qw($VERSION @ISA);
  2         2  
  2         919  
14              
15             @ISA = qw(GPS::Base GPS::Serial);
16              
17             $VERSION = sprintf("%d.%02d", q$Revision: 1.33 $ =~ /(\d+)\.(\d+)/);
18              
19             #$|++; # XXX should not be here
20              
21             sub new {
22 3     3 0 140 my $class = shift;
23 3         10 my %param = @_;
24 3   100     20 $param{'Protocol'} ||= 'GRMN';
25              
26 3         31 my $self = $class->SUPER::common_new(%param);
27 2         4 bless $self, $class;
28              
29             # Use the generic handler for protocol initialization
30 2         17 $self->{handler} = GPS::Garmin::Handler::Generic->new($self);
31             # Initialize protocol
32 2 50       5 $self->get_product_id unless $param{do_not_init};
33              
34 2         8 $self;
35             }
36              
37             sub DESTROY {
38 3     3   165 my $self = shift;
39 3 50       15 if ($self->serial) {
40 0           $self->abort_transfer;
41             }
42             }
43              
44 0     0 0   sub records { shift->{records} }
45              
46 0     0 0   sub protocol { shift->{protocol} }
47              
48 0     0 0   sub product_id { shift->{product_id} }
49              
50 0     0 0   sub software_version { shift->{software_version} }
51              
52 0     0 0   sub product_description { shift->{product_description} }
53              
54 0     0 0   sub handler { shift->{handler} }
55              
56             *device_name = \&product_description;
57              
58             sub cur_pid {
59 0     0 0   my $self = shift;
60 0 0         @_ ? ($self->{cur_pid} = shift) : $self->{cur_pid};
61             }
62              
63             sub cur_request {
64 0     0 0   my $self = shift;
65             @_ ? ($self->{cur_request} = shift) : $self->{cur_request}
66 0 0         }
67              
68             # - Packet ID Type - What's the packet all about?
69             sub Pid_Byte {
70 0     0 0   my $self = shift;
71 2     2   13 no strict 'refs';
  2         2  
  2         2912  
72              
73             #Get the ID's from the constants
74             #This is were we get
75             #the subroutine names in GPS::Garmin::Handler
76              
77 0 0         unless (ref($self->{pidbytes}) eq 'ARRAY') {
78 0           for(@{$GPS::Garmin::Constant::EXPORT_TAGS{pids}}) {
  0            
79 0           my ($tag) = /GRMN_(\w+)/;
80 0           $self->{pidbytes}[&$_] = ucfirst(lc($tag));
81             }
82             }
83              
84 0           my $b = shift;
85 0   0       return $self->{pidbytes}[$b] || sprintf("0x%.2x",$b);
86             }
87              
88             sub get_position {
89 0     0 0   shift->send_command(GRMN_TRANSFER_POSN);
90             }
91              
92             sub get_time {
93 0     0 0   shift->send_command(GRMN_TRANSFER_TIME);
94             }
95              
96             sub power_off {
97 0     0 0   shift->send_command(GRMN_TURN_OFF_PWR);
98             }
99              
100             sub abort_transfer {
101 0     0 0   shift->send_command(GRMN_ABORT_TRANSFER,no_reply=>1);
102             }
103              
104              
105              
106             sub upload_data {
107             #This is still very experimental
108              
109 0     0 0   my $self = shift;
110 0           my $aref = shift;
111 0           my $cb = shift;
112 0           my $recn = @$aref;
113 0           my $records = pack("l", $recn+1);
114              
115             # Tell the Garmin how many are coming.
116             RNUM: {
117 0           $self->send_packet(GRMN_RECORDS,$records);
  0            
118 0 0         redo RNUM if $self->get_reply(1) == GRMN_NAK;
119             }
120              
121 0           my $i = 0;
122 0           for (@$aref) {
123 0           $self->send_packet(@$_);
124 0           $self->get_reply(1);
125 0 0         $cb->($i) if $cb;
126 0           $i++;
127             }
128 0           $self->send_packet(GRMN_XFER_CMPLT);
129             }
130              
131              
132             sub prepare_transfer {
133 0     0 0   my $self = shift;
134 0           my $t = lc shift;
135              
136 0           my %cmd = ( wpt=>GRMN_TRANSFER_WPT,
137             trk=>GRMN_TRANSFER_TRK,
138             alm=>GRMN_TRANSFER_ALM,
139             waypoint=>GRMN_TRANSFER_WPT,
140             track=>GRMN_TRANSFER_TRK,
141             almanac=>GRMN_TRANSFER_WPT,
142             rte=>GRMN_TRANSFER_RTE,
143             route=>GRMN_TRANSFER_RTE,
144             );
145              
146 0 0         if($cmd{$t}) {
147 0           $self->send_command($cmd{$t});
148 0           $self->cur_request($t);
149             }
150             }
151              
152             sub get_product_id {
153             #returns (product_id,software_version,product_description)
154 0     0 0   my $self = shift;
155 0           $self->send_packet(GRMN_PRODUCT_RQST);
156 0           my @result = $self->get_reply;
157              
158 0 0         if ($result[0] == GRMN_NAK) {
159 0           $self->usleep(50);
160 0           return $self->get_product_id;
161             }
162              
163 0           $self->{product_id} = $result[0];
164 0           $self->{software_version} = $result[1];
165 0           $self->{product_description} = $result[2];
166              
167 0 0 0       if ( $self->{product_id} == 694 # etrex vista hcx
    0 0        
      0        
      0        
      0        
168             ) {
169 0           $self->{handler} = GPS::Garmin::Handler::EtrexVistaHCx->new($self);
170             } elsif ( $self->{product_id} == 154 # etrex venture
171             || $self->{product_id} == 169 # etrex vista
172             || $self->{product_id} == 315 # etrex vista c
173             || $self->{product_id} == 111 # emap
174             || $self->{product_id} == 248 # gecko 201
175             || $self->{product_id} == 292 # GPSmap 60CSx
176             # XXX add more devices here ...
177             ) {
178 0           $self->{handler} = GPS::Garmin::Handler::EtrexVenture->new($self);
179             } else {
180 0           warn "Unknown product id $self->{product_id}, fallback to generic handler.\n";
181 0           $self->{handler} = GPS::Garmin::Handler::Generic->new($self);
182             }
183 0           return @result;
184             }
185              
186             #Converts decimal coordinates to (N|E|W|S)Deg"Min
187             sub long_coords {
188 0     0 0   my ($self,$lat,$lon) = @_;
189 0           my $ltcord = "N";
190 0 0         $ltcord = "S" if $lat < 0;
191 0           my $lncord = "E";
192 0 0         $lncord = "W" if $lon < 0;
193 0           $lat = abs($lat);
194 0           $lon = abs($lon);
195 0           $lat = int($lat)+($lat - int($lat))*60/100;
196 0           $lon = int($lon)+($lon - int($lon))*60/100;
197 0           return($ltcord,$lat,$lncord,$lon);
198             }
199              
200             # - Checksum calculation according to Garmin specs.
201             sub checksum {
202 0     0 0   my $self = shift;
203 0           my $csum;
204 0           for (unpack "C*",shift) {
205 0           $csum -= $_;
206 0           $csum %= 256; #Is this trustable with negative numbers?
207             }
208 0           $csum;
209             }
210              
211             # - Semicircle to degrees
212             sub semicirc_deg {
213 0     0 0   my $self = shift;
214 0           return shift() * (180/2**31);
215             }
216              
217             sub deg_semicirc {
218 0     0 0   my $self = shift;
219 0           return shift() * (2**31/180);
220             }
221              
222             sub read_packet {
223             #gets a packet from the device, returns (data,command)
224             #if any arg is given, it will consider a whole packet,
225             #Otherwise, it'll assume that command is already read,
226             #starting at length and returning undef in command.
227              
228 0     0 0   my $self = shift;
229 0           my ($command,$data);
230              
231 0 0         if(@_) {
232 0           while(my $buf = unpack "C", $self->_read) {
233 0           $self->usleep(1);
234 0 0         next if $buf != $self->cur_pid;
235 0           $self->usleep(10);
236             }
237 0           my $command = $self->_read;
238             }
239              
240 0           my $len = $self->safe_read;
241 0           my $lenc = unpack("C",$len);
242              
243 0           $self->usleep(1);
244 0           for(1..$lenc) {
245 0 0         $self->usleep(1) if (($_ % 6) == 0);
246 0           $data .= $self->safe_read
247             }
248              
249 0           my $csum = $self->safe_read;
250 0           $self->_read(2);#Footer
251 0           my $full_packet = pack("C",$self->cur_pid).$len.$data;
252 0 0         if (pack("C",$self->checksum($full_packet)) ne $csum) {
253              
254 0 0         printf STDERR "NAK: %s != %s\n",$self->checksum($data),unpack"C",$csum
255             if $self->verbose;
256              
257 0           $self->_read(2);
258 0           $self->send_packet(GRMN_NAK);
259 0           $self->usleep(50);
260 0           return $self->read_packet(shift,1);
261             }
262              
263 0           return ($data,$command);
264             }
265              
266              
267             sub grab {
268 0     0 0   my $self = shift;
269 0 0         die "Must use prepare_transfer first!" unless $self->cur_request;
270              
271 0           my @result = $self->get_reply;
272              
273             # XXX ignore it for now, because GRMN_NAK is the same as ID=021!
274             # if ($result[0] == GRMN_NAK) {
275             # printf STDERR "Received NAK in grab\n" if $self->verbose;
276             # $self->usleep(50);
277             # return $self->grab;
278             # }
279              
280 0           return @result;
281             }
282              
283              
284             sub send_command ($) {
285             #Sends Command to GPS
286             #and starts get_reply so that a Garmin::Handler
287             #takes care of the reply
288             #returns Garmin::Handler reply
289              
290 0     0 0   my $self = shift;
291 0           my $command = shift;
292 0           my %p = @_;
293 0           $self->send_packet(GRMN_COMMAND_DATA,pack("C2",$command,GRMN_NUL));
294              
295 0 0         my @result = $self->get_reply() unless $p{no_reply};
296              
297 0 0 0       if (@result && $result[0] == GRMN_NAK) {
298 0 0         printf STDERR "Received NAK in send_command\n" if $self->verbose;
299 0           $self->usleep(50);
300 0           return $self->send_command($command,%p);
301             }
302              
303 0           return @result;
304             }
305              
306             sub send_packet {
307             #Prepares the packet and sends it
308             #first argument is command in decimal
309             #following arguments are treated as already been packed
310              
311 0     0 0   my $self = shift;
312 0           my $message = pack("C",shift);
313 0 0         if(@_) {
314 0           my $buf = join('',@_);
315 0           $message .= pack("C",length($buf)).$buf;
316             } else {
317 0           $message .= pack("C2",GRMN_TRANSFER_ALM,GRMN_TRANSFER_ALM);
318             }
319 0           $message .= pack "C1",$self->checksum($message);
320 0           $message = $self->escape_dle($message);
321 0           $message = GRMN_HEADER . $message . GRMN_FOOTER;
322 0 0         print STDERR "SENDING PACKET: (", join ' ',(map {$self->Pid_Byte($_)}unpack("C*",$message)),")","\n" if $self->verbose;
  0            
323 0           $self->usleep(20);
324 0           $self->_write($message);
325             }
326              
327             sub escape_dle {
328             #\x10 must become \x10\x10
329 0     0 0   my $self = shift;
330 0           my $buf = shift;
331              
332 0           my $i = index($buf,"\x10");
333 0 0         if($i > -1) {
334 0           for (my $i=0;$i
335 0 0         $i=index($buf,"\x10",$i); last if $i == -1;
  0            
336 0           substr($buf,$i,1,"\x10\x10");
337 0           $i+=2;
338             }
339             }
340 0           return $buf;
341             }
342              
343             sub get_reply {
344 2     2   15 no strict "subs";
  2         10  
  2         1152  
345 0     0 0   my $self = shift;
346 0           my $command = shift;
347 0           my $handler = $self->handler;
348              
349 0 0         print STDERR "RECEBI:\n" if $self->{'verbose'};
350              
351 0     0     local $SIG{ALRM} = sub {die "GPS Device has timed out\n"};
  0            
352 0           eval { alarm($self->{timeout}) };
  0            
353              
354 0           while (1) {
355             # $self->usleep(10); # XXX try it with a smaller delay...
356 0           $self->usleep(1);
357 0           my $buf = unpack "C1",$self->_read;
358              
359 0 0 0       if (defined $buf && $buf == GRMN_DLE) { #Start processing Garmin data
360 0           $buf = $self->_read;
361 0           $buf = unpack("C1",$buf);
362 0 0         next if $buf == GRMN_NUL;#0 byte
363              
364 0 0         if ($buf == GRMN_ETX) {
365 0 0         print STDERR ";\n" if $self->{'verbose'};
366 0           eval { alarm($self->{timeout}) };
  0            
367 0           next;
368             }
369 0           my $gcommand = $self->Pid_Byte($buf);
370 0 0         next unless defined $gcommand;
371              
372 0 0         my $is_prot = 1 if($gcommand =~ /byte$/);
373              
374 0 0         print STDERR "\nGot $gcommand\n" if $self->verbose;
375 0           $self->cur_pid($buf);
376              
377 0 0         my @data = $handler->$gcommand($command) if $handler->can($gcommand);
378             ##XXX ??? $data[0] is normal data?!
379 0 0 0       if ($gcommand !~ /^(?:Wpt|Trk|Rte)_(?:data|hdr)$/ && $data[0] == GRMN_ACK) {
380 0 0         next unless $command;
381             }
382 0           eval {alarm 0; };
  0            
383             ##XXX ??? $data[0] is normal data?!
384             #warn $gcommand;
385 0 0 0       if ($gcommand !~ /^(?:Wpt|Trk|Rte)_(?:data|hdr)$/ && $data[0] == GRMN_NAK) {
386 0 0         print STDERR "First byte is NAK\n" if ($self->verbose);
387 0           return GRMN_NAK;
388             }
389 0 0         return $data[0] if @data == 1;
390 0           return @data;
391             }
392             }
393 0           eval {alarm 0; };
  0            
394 0 0         print STDERR "No ACK seen, returning NAK\n\n" if ($self->verbose);
395 0           return GRMN_NAK;
396             }
397              
398              
399              
400              
401             1;
402             __END__