File Coverage

blib/lib/Device/GPS.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             # Copyright (c) 2015 Timm Murray
2             # All rights reserved.
3             #
4             # Redistribution and use in source and binary forms, with or without
5             # modification, are permitted provided that the following conditions are met:
6             #
7             # * Redistributions of source code must retain the above copyright notice,
8             # this list of conditions and the following disclaimer.
9             # * Redistributions in binary form must reproduce the above copyright
10             # notice, this list of conditions and the following disclaimer in the
11             # documentation and/or other materials provided with the distribution.
12             #
13             # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14             # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15             # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16             # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
17             # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18             # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19             # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20             # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21             # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22             # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
23             # POSSIBILITY OF SUCH DAMAGE.
24             package Device::GPS;
25             $Device::GPS::VERSION = '0.714874475569562';
26 2     2   23353 use v5.14;
  2         7  
  2         102  
27 2     2   12 use warnings;
  2         3  
  2         69  
28 2     2   543 use Moose;
  2         428756  
  2         18  
29 2     2   16029 use namespace::autoclean;
  0            
  0            
30             use Device::GPS::Connection;
31              
32             # ABSTRACT: Read GPS (NMEA) data over a wire
33              
34             use constant {
35             'CALLBACK_POSITION' => '$GPGGA',
36             'CALLBACK_ACTIVE_SATS' => '$GPGSA',
37             'CALLBACK_SATS_IN_VIEW' => '$GPGSV',
38             'CALLBACK_REC_MIN' => '$GPRMC',
39             'CALLBACK_GEO_LOC' => '$GPGLL',
40             'CALLBACK_VELOCITY' => '$GPVTG',
41             };
42              
43             has 'connection' => (
44             is => 'ro',
45             isa => 'Device::GPS::Connection',
46             required => 1,
47             );
48              
49             has '_callbacks' => (
50             is => 'ro',
51             isa => 'HashRef[ArrayRef[CodeRef]]',
52             default => sub {{
53             CALLBACK_POSITION => [],
54             CALLBACK_ACTIVE_SATS => [],
55             CALLBACK_SATS_IN_VIEW => [],
56             CALLBACK_REC_MIN => [],
57             CALLBACK_GEO_LOC => [],
58             CALLBACK_VELOCITY => [],
59             }},
60             );
61              
62              
63             sub add_callback
64             {
65             my ($self, $type, $callback) = @_;
66             push @{ $self->_callbacks->{$type} }, $callback;
67             return 1;
68             }
69              
70             sub parse_next
71             {
72             my ($self) = @_;
73             my $sentence = $self->connection->read_nmea_sentence;
74             return unless $sentence;
75             my ($type, @data) = split /,/, $sentence;
76             my $checksum = pop @data;
77             # TODO verify checksum
78             @data = $self->_convert_data_by_type( $type, @data );
79              
80             foreach my $callback (@{ $self->_callbacks->{$type} }) {
81             $callback->(@data);
82             }
83              
84             return 1;
85             }
86              
87             sub _convert_data_by_type
88             {
89             my ($self, $type, @data) = @_;
90             $type =~ s/\A\$//;
91             my $method = '_convert_data_for_' . $type;
92             @data = $self->$method( @data ) if $self->can( $method );
93             return @data;
94             }
95              
96             sub _convert_data_for_GPGGA
97             {
98             my ($self, @data) = @_;
99              
100             my $convert = sub {
101             my ($datapoint) = @_;
102             my ($deg, $arcmin, $arcsec) = $datapoint =~ /\A
103             (\d{2,3})
104             (\d{2})
105             \.
106             (\d+)
107             \z/x;
108              
109             return ($deg, $arcmin, $arcsec);
110             };
111              
112             splice @data, 3, 1, $convert->( $data[3] );
113             splice @data, 1, 1, $convert->( $data[1] );
114             return @data;
115             }
116              
117              
118             no Moose;
119             __PACKAGE__->meta->make_immutable;
120             1;
121             __END__
122              
123             =head1 NAME
124              
125             Device::GPS - Read GPS (NMEA) data over a wire
126              
127             =head1 SYNOPSIS
128              
129             my $gps = Device::GPS->new({
130             connection => Device::GPS::Connection::Serial->new({
131             port => '/dev/ttyACM0',
132             baud => 9600,
133             }),
134             });
135             $gps->add_callback( $gps->CALLBACK_POSITION, sub {
136             my ($time, $lat_deg, $lat_min, $lat_sec, $ns,
137             $long_deg, $long_min, $long_sec, $ew,
138             $quality, $satellites, $horz_dil, $altitude, $height,
139             $time_since_last_dgps, $dgps_station_id) = @_;
140             say "Lat: $lat_deg deg $lat_min.$lat_sec' $ns";
141             say "Long: $long_deg deg $long_min.$lat_sec' $ew";
142             });
143              
144              
145             while(1) { $gps->parse_next }
146              
147             =head1 DESCRIPTION
148              
149             Captures GPS data using a callback system.
150              
151             =head1 METHODS
152              
153             =head2 new
154              
155             my $gps = Device::GPS->new({
156             connection => Device::GPS::Connection::Serial->new({
157             port => '/dev/ttyACM0',
158             baud => 9600,
159             }),
160             });
161              
162             Constructor. The C<connection> parameter needs to be some object that
163             does the C<Device::GPS::Connection> role.
164              
165             =head2 parse_next
166              
167             Call this continually in a loop to get the next set of data.
168              
169             =head2 add_callback
170              
171             add_callback( $callback_type, $callback )
172              
173             Set a callback that will be called when we parse a given GPS command.
174             There are constants provided for the types. Below is a list of the
175             constants and the arguments your callback will get for each one.
176              
177             =head3 CALLBACK_POSITION (GPGGA)
178              
179             ($time, $lat_deg, $lat_min, $lat_sec, $ns,
180             $long_deg, $long_min, $long_sec, $ew,
181             $quality, $satellites, $horz_dil, $altitude, $height,
182             $time_since_last_dgps, $dgps_station_id);
183              
184             =over
185              
186             =item * time - Time of capture
187              
188             =item * lat_deg - Latitude degrees
189              
190             =item * lat_min - Latitude arcminutes
191              
192             =item * lat_sec - Latitude arcseconds
193              
194             =item * ns - "N" or "S" for latitude
195              
196             =item * long_deg - Longitude degrees
197              
198             =item * long_min - Longitude arcminutes
199              
200             =item * long_sec - Longitude arcseconds
201              
202             =item * ew - "E" or "W" for longitude
203              
204             =item * quality - Quality number
205              
206             =item * satellites - Number of satellites being tracked
207              
208             =item * horz_dil - Horizontal dilution of position
209              
210             =item * altitude - Altitude in meters above sealevel
211              
212             =item * height - Height of geoid
213              
214             =item * time_since_last_dgps - time (in seconds) since last DGPS update
215              
216             =item * dgps_station_id - DGPS station ID number
217              
218             =back
219              
220             =head3 CALLBACK_VELOCITY (GPVTG)
221              
222             ($true_track, 'T', $mag_track, 'M', $ground_speed_knots, 'N',
223             $ground_speed_kph, 'K');
224              
225             =over
226              
227             =item * true_track - True track made good (degrees)
228              
229             =item * mag_track - Magnetic track made good
230              
231             =item * ground_speed_knots - Ground speed in knots
232              
233             =item * ground_speed_kph - Ground speed in kph
234              
235             =back
236              
237             =for TODO
238              
239             CALLBACK_ACTIVE_SATS (GPGSA)
240             CALLBACK_SATS_IN_VIEW (GPGSV)
241             CALLBACK_REC_MIN (GPRMC)
242             CALLBACK_GEO_LOC (GPGLL)
243              
244             =cut
245              
246             =head1 SEE ALSO
247              
248             L<GPS::NMEA>
249              
250             =head1 LICENSE
251              
252             Copyright (c) 2015 Timm Murray
253             All rights reserved.
254              
255             Redistribution and use in source and binary forms, with or without
256             modification, are permitted provided that the following conditions are met:
257              
258             * Redistributions of source code must retain the above copyright notice,
259             this list of conditions and the following disclaimer.
260             * Redistributions in binary form must reproduce the above copyright
261             notice, this list of conditions and the following disclaimer in the
262             documentation and/or other materials provided with the distribution.
263              
264             THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
265             AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
266             IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
267             ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
268             LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
269             CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
270             SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
271             INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
272             CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
273             ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
274             POSSIBILITY OF SUCH DAMAGE.
275              
276             =cut