File Coverage

blib/lib/GPS/Garmin.pm
Criterion Covered Total %
statement 34 213 15.9
branch 2 82 2.4
condition 2 35 5.7
subroutine 10 37 27.0
pod 0 27 0.0
total 48 394 12.1


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   2215 use GPS::Base ();
  2         5  
  2         36  
8 2     2   992 use GPS::Serial ();
  2         18  
  2         42  
9 2     2   1398 use GPS::Garmin::Handler ();
  2         6  
  2         51  
10 2     2   11 use GPS::Garmin::Constant ':all';
  2         2  
  2         3782  
11              
12 2     2   13 use strict;
  2         10  
  2         87  
13 2     2   11 use vars qw($VERSION @ISA);
  2         4  
  2         923  
14              
15             @ISA = qw(GPS::Base GPS::Serial);
16              
17             $VERSION = sprintf("%d.%02d", q$Revision: 1.34 $ =~ /(\d+)\.(\d+)/);
18              
19             #$|++; # XXX should not be here
20              
21             sub new {
22 3     3 0 479 my $class = shift;
23 3         10 my %param = @_;
24 3   100     23 $param{'Protocol'} ||= 'GRMN';
25              
26 3         27 my $self = $class->SUPER::common_new(%param);
27 2         5 bless $self, $class;
28              
29             # Use the generic handler for protocol initialization
30 2         19 $self->{handler} = GPS::Garmin::Handler::Generic->new($self);
31             # Initialize protocol
32 2 50       6 $self->get_product_id unless $param{do_not_init};
33              
34 2         6 $self;
35             }
36              
37             sub DESTROY {
38 3     3   434 my $self = shift;
39 3 50       16 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 0 0         @_ ? ($self->{cur_request} = shift) : $self->{cur_request}
66             }
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   11 no strict 'refs';
  2         3  
  2         3318  
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 0           RNUM: {
117 0           $self->send_packet(GRMN_RECORDS,$records);
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        
      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             || $self->{product_id} == 295 # etrex yellow
177             # XXX add more devices here ...
178             ) {
179 0           $self->{handler} = GPS::Garmin::Handler::EtrexVenture->new($self);
180             } else {
181 0           warn "Unknown product id $self->{product_id}, fallback to generic handler.\n";
182 0           $self->{handler} = GPS::Garmin::Handler::Generic->new($self);
183             }
184 0           return @result;
185             }
186              
187             #Converts decimal coordinates to (N|E|W|S)Deg"Min
188             sub long_coords {
189 0     0 0   my ($self,$lat,$lon) = @_;
190 0           my $ltcord = "N";
191 0 0         $ltcord = "S" if $lat < 0;
192 0           my $lncord = "E";
193 0 0         $lncord = "W" if $lon < 0;
194 0           $lat = abs($lat);
195 0           $lon = abs($lon);
196 0           $lat = int($lat)+($lat - int($lat))*60/100;
197 0           $lon = int($lon)+($lon - int($lon))*60/100;
198 0           return($ltcord,$lat,$lncord,$lon);
199             }
200              
201             # - Checksum calculation according to Garmin specs.
202             sub checksum {
203 0     0 0   my $self = shift;
204 0           my $csum;
205 0           for (unpack "C*",shift) {
206 0           $csum -= $_;
207 0           $csum %= 256; #Is this trustable with negative numbers?
208             }
209 0           $csum;
210             }
211              
212             # - Semicircle to degrees
213             sub semicirc_deg {
214 0     0 0   my $self = shift;
215 0           return shift() * (180/2**31);
216             }
217              
218             sub deg_semicirc {
219 0     0 0   my $self = shift;
220 0           return shift() * (2**31/180);
221             }
222              
223             sub read_packet {
224             #gets a packet from the device, returns (data,command)
225             #if any arg is given, it will consider a whole packet,
226             #Otherwise, it'll assume that command is already read,
227             #starting at length and returning undef in command.
228              
229 0     0 0   my $self = shift;
230 0           my ($command,$data);
231              
232 0 0         if(@_) {
233 0           while(my $buf = unpack "C", $self->_read) {
234 0           $self->usleep(1);
235 0 0         next if $buf != $self->cur_pid;
236 0           $self->usleep(10);
237             }
238 0           my $command = $self->_read;
239             }
240              
241 0           my $len = $self->safe_read;
242 0           my $lenc = unpack("C",$len);
243              
244 0           $self->usleep(1);
245 0           for(1..$lenc) {
246 0 0         $self->usleep(1) if (($_ % 6) == 0);
247 0           $data .= $self->safe_read
248             }
249              
250 0           my $csum = $self->safe_read;
251 0           $self->_read(2);#Footer
252 0           my $full_packet = pack("C",$self->cur_pid).$len.$data;
253 0 0         if (pack("C",$self->checksum($full_packet)) ne $csum) {
254              
255 0 0         printf STDERR "NAK: %s != %s\n",$self->checksum($data),unpack"C",$csum
256             if $self->verbose;
257              
258 0           $self->_read(2);
259 0           $self->send_packet(GRMN_NAK);
260 0           $self->usleep(50);
261 0           return $self->read_packet(shift,1);
262             }
263              
264 0           return ($data,$command);
265             }
266              
267              
268             sub grab {
269 0     0 0   my $self = shift;
270 0 0         die "Must use prepare_transfer first!" unless $self->cur_request;
271              
272 0           my @result = $self->get_reply;
273              
274             # XXX ignore it for now, because GRMN_NAK is the same as ID=021!
275             # if ($result[0] == GRMN_NAK) {
276             # printf STDERR "Received NAK in grab\n" if $self->verbose;
277             # $self->usleep(50);
278             # return $self->grab;
279             # }
280              
281 0           return @result;
282             }
283              
284              
285             sub send_command ($) {
286             #Sends Command to GPS
287             #and starts get_reply so that a Garmin::Handler
288             #takes care of the reply
289             #returns Garmin::Handler reply
290              
291 0     0 0   my $self = shift;
292 0           my $command = shift;
293 0           my %p = @_;
294 0           $self->send_packet(GRMN_COMMAND_DATA,pack("C2",$command,GRMN_NUL));
295              
296 0 0         my @result = $self->get_reply() unless $p{no_reply};
297              
298 0 0 0       if (@result && $result[0] == GRMN_NAK) {
299 0 0         printf STDERR "Received NAK in send_command\n" if $self->verbose;
300 0           $self->usleep(50);
301 0           return $self->send_command($command,%p);
302             }
303              
304 0           return @result;
305             }
306              
307             sub send_packet {
308             #Prepares the packet and sends it
309             #first argument is command in decimal
310             #following arguments are treated as already been packed
311              
312 0     0 0   my $self = shift;
313 0           my $message = pack("C",shift);
314 0 0         if(@_) {
315 0           my $buf = join('',@_);
316 0           $message .= pack("C",length($buf)).$buf;
317             } else {
318 0           $message .= pack("C2",GRMN_TRANSFER_ALM,GRMN_TRANSFER_ALM);
319             }
320 0           $message .= pack "C1",$self->checksum($message);
321 0           $message = $self->escape_dle($message);
322 0           $message = GRMN_HEADER . $message . GRMN_FOOTER;
323 0 0         print STDERR "SENDING PACKET: (", join ' ',(map {$self->Pid_Byte($_)}unpack("C*",$message)),")","\n" if $self->verbose;
  0            
324 0           $self->usleep(20);
325 0           $self->_write($message);
326             }
327              
328             sub escape_dle {
329             #\x10 must become \x10\x10
330 0     0 0   my $self = shift;
331 0           my $buf = shift;
332              
333 0           my $i = index($buf,"\x10");
334 0 0         if($i > -1) {
335 0           for (my $i=0;$i
336 0 0         $i=index($buf,"\x10",$i); last if $i == -1;
  0            
337 0           substr($buf,$i,1,"\x10\x10");
338 0           $i+=2;
339             }
340             }
341 0           return $buf;
342             }
343              
344             sub get_reply {
345 2     2   11 no strict "subs";
  2         4  
  2         1066  
346 0     0 0   my $self = shift;
347 0           my $command = shift;
348 0           my $handler = $self->handler;
349              
350 0 0         print STDERR "RECEBI:\n" if $self->{'verbose'};
351              
352 0     0     local $SIG{ALRM} = sub {die "GPS Device has timed out\n"};
  0            
353 0           eval { alarm($self->{timeout}) };
  0            
354              
355 0           while (1) {
356             # $self->usleep(10); # XXX try it with a smaller delay...
357 0           $self->usleep(1);
358 0           my $buf = unpack "C1",$self->_read;
359              
360 0 0 0       if (defined $buf && $buf == GRMN_DLE) { #Start processing Garmin data
361 0           $buf = $self->_read;
362 0           $buf = unpack("C1",$buf);
363 0 0         next if $buf == GRMN_NUL;#0 byte
364              
365 0 0         if ($buf == GRMN_ETX) {
366 0 0         print STDERR ";\n" if $self->{'verbose'};
367 0           eval { alarm($self->{timeout}) };
  0            
368 0           next;
369             }
370 0           my $gcommand = $self->Pid_Byte($buf);
371 0 0         next unless defined $gcommand;
372              
373 0 0         my $is_prot = 1 if($gcommand =~ /byte$/);
374              
375 0 0         print STDERR "\nGot $gcommand\n" if $self->verbose;
376 0           $self->cur_pid($buf);
377              
378 0 0         my @data = $handler->$gcommand($command) if $handler->can($gcommand);
379             ##XXX ??? $data[0] is normal data?!
380 0 0 0       if ($gcommand !~ /^(?:Wpt|Trk|Rte)_(?:data|hdr)$/ && $data[0] == GRMN_ACK) {
381 0 0         next unless $command;
382             }
383 0           eval {alarm 0; };
  0            
384             ##XXX ??? $data[0] is normal data?!
385             #warn $gcommand;
386 0 0 0       if ($gcommand !~ /^(?:Wpt|Trk|Rte)_(?:data|hdr)$/ && $data[0] == GRMN_NAK) {
387 0 0         print STDERR "First byte is NAK\n" if ($self->verbose);
388 0           return GRMN_NAK;
389             }
390 0 0         return $data[0] if @data == 1;
391 0           return @data;
392             }
393             }
394 0           eval {alarm 0; };
  0            
395 0 0         print STDERR "No ACK seen, returning NAK\n\n" if ($self->verbose);
396 0           return GRMN_NAK;
397             }
398              
399              
400              
401              
402             1;
403             __END__