├── NeoGPS
├── CosaCompat.h
├── DMS.cpp
├── DMS.h
├── GPSTime.cpp
├── GPSTime.h
├── GPSfix.h
├── GPSfix_cfg.h
├── GPSport.h
├── Location.cpp
├── Location.h
├── NMEAGPS.cpp
├── NMEAGPS.h
├── NMEAGPS_cfg.h
├── NMEAGPSprivate.h
├── NeoGPS_cfg.h
├── NeoTime.cpp
├── NeoTime.h
├── README.md
├── Streamers.cpp
└── Streamers.h
├── OpenDsky.ino
├── Program.h
├── README.md
├── Sound.cpp
├── Sound.h
└── audio_files .zip
/NeoGPS/CosaCompat.h:
--------------------------------------------------------------------------------
1 | #ifndef COSACOMPAT_H
2 | #define COSACOMPAT_H
3 |
4 | // Copyright (C) 2014-2017, SlashDevin
5 | //
6 | // This file is part of NeoGPS
7 | //
8 | // NeoGPS is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation, either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // NeoGPS is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with NeoGPS. If not, see .
20 |
21 | #ifdef __AVR__
22 |
23 | #include
24 |
25 | #else
26 |
27 | #define PGM_P const char *
28 |
29 | #endif
30 |
31 | typedef PGM_P str_P;
32 | #define __PROGMEM PROGMEM
33 |
34 | #endif
--------------------------------------------------------------------------------
/NeoGPS/DMS.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2014-2017, SlashDevin
2 | //
3 | // This file is part of NeoGPS
4 | //
5 | // NeoGPS is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // NeoGPS is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with NeoGPS. If not, see .
17 |
18 | #include "DMS.h"
19 |
20 | #include
21 |
22 | //----------------------------------------------------------------
23 | // Note that no division is used, and shifts are on byte boundaries. Fast!
24 |
25 | void DMS_t::From( int32_t deg_1E7 )
26 | {
27 | const uint32_t E7 = 10000000UL;
28 |
29 | if (deg_1E7 < 0) {
30 | deg_1E7 = -deg_1E7;
31 | hemisphere = SOUTH_H; // or WEST_H
32 | } else
33 | hemisphere = NORTH_H; // or EAST_H
34 |
35 | const uint32_t div_E32 = 429; // 1e-07 * 2^32
36 | degrees = ((deg_1E7 >> 16) * div_E32) >> 16;
37 | uint32_t remainder = deg_1E7 - degrees * E7;
38 |
39 | remainder *= 60; // to minutes * E7
40 | minutes = ((remainder >> 16) * div_E32) >> 16;
41 | remainder -= minutes * E7;
42 |
43 | remainder *= 60; // to seconds * E7
44 | uint32_t secs = ((remainder >> 16) * div_E32) >> 16;
45 | remainder -= secs * E7;
46 |
47 | const uint32_t div_1E4_E24 = 1677; // 0.00001 * 2^24
48 | seconds_frac = (((remainder >> 8) * div_1E4_E24) >> 16); // thousandths
49 | seconds_whole = secs;
50 |
51 | // Carry if thousandths too big
52 | if (seconds_frac >= 1000) {
53 | seconds_frac -= 1000;
54 | seconds_whole++;
55 | if (seconds_whole >= 60) {
56 | seconds_whole -= 60;
57 | minutes++;
58 | if (minutes >= 60) {
59 | minutes -= 60;
60 | degrees++;
61 | }
62 | }
63 | }
64 |
65 | } // From
66 |
67 | //----------------------------------------------------------------
68 |
69 | Print & operator << ( Print & outs, const DMS_t & dms )
70 | {
71 | if (dms.degrees < 10)
72 | outs.write( '0' );
73 | outs.print( dms.degrees );
74 | outs.write( ' ' );
75 | if (dms.minutes < 10)
76 | outs.write( '0' );
77 | outs.print( dms.minutes );
78 | outs.print( F("\' ") );
79 | if (dms.seconds_whole < 10)
80 | outs.write( '0' );
81 | outs.print( dms.seconds_whole );
82 | outs.write( '.' );
83 | if (dms.seconds_frac < 100)
84 | outs.write( '0' );
85 | if (dms.seconds_frac < 10)
86 | outs.write( '0' );
87 | outs.print( dms.seconds_frac );
88 | outs.print( F("\" ") );
89 |
90 | return outs;
91 |
92 | } // operator <<
93 |
94 | //----------------------------------------------------------------
95 |
96 | void DMS_t::printDDDMMmmmm( Print & outs ) const
97 | {
98 | outs.print( degrees );
99 |
100 | if (minutes < 10)
101 | outs.print( '0' );
102 | outs.print( minutes );
103 | outs.print( '.' );
104 |
105 | // Calculate the fractional minutes from the seconds,
106 | // *without* using floating-point numbers.
107 |
108 | uint16_t mmmm = seconds_whole * 166; // same as 10000/60, less .66666...
109 | mmmm += (seconds_whole * 2 + seconds_frac/2 ) / 3; // ... plus the remaining .66666
110 | // ... plus the seconds_frac, scaled by 10000/(60*1000) = 1/6, which
111 | // is implemented above as 1/2 * 1/3
112 |
113 | // print leading zeroes, if necessary
114 | if (mmmm < 1000)
115 | outs.print( '0' );
116 | if (mmmm < 100)
117 | outs.print( '0' );
118 | if (mmmm < 10)
119 | outs.print( '0' );
120 | outs.print( mmmm );
121 | }
--------------------------------------------------------------------------------
/NeoGPS/DMS.h:
--------------------------------------------------------------------------------
1 | #ifndef DMS_H
2 | #define DMS_H
3 |
4 | // Copyright (C) 2014-2017, SlashDevin
5 | //
6 | // This file is part of NeoGPS
7 | //
8 | // NeoGPS is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation, either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // NeoGPS is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with NeoGPS. If not, see .
20 |
21 | #include "NeoGPS_cfg.h"
22 | #include
23 | class Print;
24 |
25 | enum Hemisphere_t { NORTH_H = 0, SOUTH_H = 1, EAST_H = 0, WEST_H = 1 };
26 |
27 | class DMS_t
28 | {
29 | public:
30 | uint8_t degrees;
31 | uint8_t minutes ;//NEOGPS_BF(6);
32 | Hemisphere_t hemisphere ;//NEOGPS_BF(2); compiler bug!
33 | uint8_t seconds_whole NEOGPS_BF(6);
34 | uint16_t seconds_frac NEOGPS_BF(10); // 1000ths
35 |
36 | void init() { degrees = minutes = seconds_whole = seconds_frac = 0;
37 | hemisphere = NORTH_H; }
38 |
39 | float secondsF() const { return seconds_whole + 0.001 * seconds_frac; };
40 | char NS () const { return (hemisphere == SOUTH_H) ? 'S' : 'N'; };
41 | char EW () const { return (hemisphere == WEST_H) ? 'W' : 'E'; };
42 |
43 | //.............................................................................
44 | // A utility function to convert from integer 'lat' or 'lon', scaled by 10^7
45 |
46 | void From( int32_t deg_1E7 );
47 |
48 | // Print DMS as the funky NMEA DDDMM.mmmm format
49 | void printDDDMMmmmm( Print & outs ) const;
50 |
51 | } NEOGPS_PACKED;
52 |
53 | extern Print & operator << ( Print & outs, const DMS_t & );
54 |
55 | #endif
--------------------------------------------------------------------------------
/NeoGPS/GPSTime.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2014-2017, SlashDevin
2 | //
3 | // This file is part of NeoGPS
4 | //
5 | // NeoGPS is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // NeoGPS is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with NeoGPS. If not, see .
17 |
18 | #include "GPSTime.h"
19 |
20 | uint8_t GPSTime::leap_seconds = 0;
21 | NeoGPS::clock_t GPSTime::s_start_of_week = 0;
22 |
--------------------------------------------------------------------------------
/NeoGPS/GPSTime.h:
--------------------------------------------------------------------------------
1 | #ifndef GPSTIME_H
2 | #define GPSTIME_H
3 |
4 | // Copyright (C) 2014-2017, SlashDevin
5 | //
6 | // This file is part of NeoGPS
7 | //
8 | // NeoGPS is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation, either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // NeoGPS is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with NeoGPS. If not, see .
20 |
21 | #include "NeoTime.h"
22 |
23 | class GPSTime
24 | {
25 | GPSTime();
26 |
27 | static NeoGPS::clock_t s_start_of_week;
28 |
29 | public:
30 |
31 | /**
32 | * GPS time is offset from UTC by a number of leap seconds. To convert a GPS
33 | * time to UTC time, the current number of leap seconds must be known.
34 | * See http://en.wikipedia.org/wiki/Global_Positioning_System#Leap_seconds
35 | */
36 | static uint8_t leap_seconds;
37 |
38 | /**
39 | * Some receivers report time WRT start of the current week, defined as
40 | * Sunday 00:00:00. To save fairly expensive date/time calculations,
41 | * the UTC start of week is cached
42 | */
43 | static void start_of_week( NeoGPS::time_t & now )
44 | {
45 | now.set_day();
46 | s_start_of_week =
47 | (NeoGPS::clock_t) now -
48 | (NeoGPS::clock_t) ((((now.day-1 ) * 24L +
49 | now.hours ) * 60L +
50 | now.minutes) * 60L +
51 | now.seconds);
52 | }
53 |
54 | static NeoGPS::clock_t start_of_week()
55 | {
56 | return s_start_of_week;
57 | }
58 |
59 | /*
60 | * Convert a GPS time-of-week to UTC.
61 | * Requires /leap_seconds/ and /start_of_week/.
62 | */
63 | static NeoGPS::clock_t TOW_to_UTC( uint32_t time_of_week )
64 | { return (NeoGPS::clock_t)
65 | (start_of_week() + time_of_week - leap_seconds); }
66 |
67 | /**
68 | * Set /fix/ timestamp from a GPS time-of-week in milliseconds.
69 | * Requires /leap_seconds/ and /start_of_week/.
70 | **/
71 | static bool from_TOWms
72 | ( uint32_t time_of_week_ms, NeoGPS::time_t &dt, uint16_t &ms )
73 | {
74 | //trace << PSTR("from_TOWms(") << time_of_week_ms << PSTR("), sow = ") << start_of_week() << PSTR(", leap = ") << leap_seconds << endl;
75 | bool ok = (start_of_week() != 0) && (leap_seconds != 0);
76 | if (ok) {
77 | NeoGPS::clock_t tow_s = time_of_week_ms/1000UL;
78 | dt = TOW_to_UTC( tow_s );
79 | ms = (uint16_t)(time_of_week_ms - tow_s*1000UL);
80 | }
81 | return ok;
82 | }
83 | };
84 |
85 | #endif
--------------------------------------------------------------------------------
/NeoGPS/GPSfix.h:
--------------------------------------------------------------------------------
1 | #ifndef GPSFIX_H
2 | #define GPSFIX_H
3 |
4 | // Copyright (C) 2014-2017, SlashDevin
5 | //
6 | // This file is part of NeoGPS
7 | //
8 | // NeoGPS is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation, either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // NeoGPS is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with NeoGPS. If not, see .
20 |
21 | #include "NeoGPS_cfg.h"
22 | #include "GPSfix_cfg.h"
23 |
24 | #if defined( GPS_FIX_DATE ) | defined( GPS_FIX_TIME )
25 | #include "NeoTime.h"
26 | #endif
27 |
28 | #ifdef GPS_FIX_LOCATION_DMS
29 | #include "DMS.h"
30 | #endif
31 |
32 | #ifdef GPS_FIX_LOCATION
33 | #include "Location.h"
34 | #endif
35 |
36 | /**
37 | * A structure for holding a GPS fix: time, position, velocity, etc.
38 | *
39 | * Because GPS devices report various subsets of a coherent fix,
40 | * this class tracks which members of the fix are being reported:
41 | * each part has its own validity flag. Also, operator |= implements
42 | * merging multiple reports into one consolidated report.
43 | *
44 | * @section Limitations
45 | * Reports are not really fused with an algorithm; if present in
46 | * the source, they are simply replaced in the destination.
47 | *
48 | */
49 |
50 | class gps_fix
51 | {
52 | public:
53 |
54 | // Default Constructor
55 | gps_fix() { init(); };
56 |
57 | //------------------------------------------------------------------
58 | // 'whole_frac' is a utility structure that holds the two parts
59 | // of a floating-point number.
60 | //
61 | // This is used for Altitude, Heading and Speed, which require more
62 | // significant digits than a 16-bit number.
63 | //
64 | // When parsing floating-point fields, the decimal point is used as
65 | // a separator for these two parts. This is much more efficient
66 | // than calling 'long' or 'floating-point' math subroutines.
67 | //
68 | // This requires knowing the exponent on the fraction when a simple type
69 | // (e.g., float or int) is needed. For example, 'altitude()' knows that
70 | // the whole part was stored as integer meters, and the fractional part
71 | // was stored as integer centimeters.
72 | //
73 | // Unless you want the speed and precision of the two integer parts, you
74 | // shouldn't have to use 'whole_frac'. Instead, use the
75 | // accessor functions for each of the specific fields for
76 | // Altitude, Heading and Speed.
77 |
78 | struct whole_frac {
79 | int16_t whole;
80 | int16_t frac;
81 | void init() { whole = 0; frac = 0; };
82 | int32_t int32_00() const { return ((int32_t)whole) * 100L + frac; };
83 | int16_t int16_00() const { return whole * 100 + frac; };
84 | int32_t int32_000() const { return whole * 1000L + frac; };
85 | float float_00() const { return ((float)whole) + ((float)frac)*0.01; };
86 | float float_000() const { return ((float)whole) + ((float)frac)*0.001; };
87 | } NEOGPS_PACKED;
88 |
89 | //--------------------------------------------------------------
90 | // Members of a GPS fix
91 | //
92 | // Each member is separately enabled or disabled by the CFG file
93 | // by #ifdef/#endif wrappers.
94 | // Each member has a storage declaration that depends on the
95 | // precision and type of data available from GPS devices.
96 | // Some members have a separate accessor function that converts the
97 | // internal storage type to a more common or convenient type
98 | // for use by an application.
99 | // For example, latitude data is internally stored as a 32-bit integer,
100 | // where the reported degrees have been multiplied by 10^7. Many
101 | // applications expect a floating-point value, so a floating-point
102 | // accessor is provided: 'latitude()'. This function converts the
103 | // internal 32-bit integer to a floating-point value, and then
104 | // divides it by 10^7. The returned value is now a floating-point
105 | // degrees value.
106 |
107 | #ifdef GPS_FIX_LOCATION
108 | NeoGPS::Location_t location;
109 |
110 | int32_t latitudeL() const { return location.lat (); };
111 | float latitude () const { return location.latF(); }; // accuracy loss
112 |
113 | int32_t longitudeL() const { return location.lon (); };
114 | float longitude () const { return location.lonF(); }; // accuracy loss
115 | #endif
116 |
117 | #ifdef GPS_FIX_LOCATION_DMS
118 | DMS_t latitudeDMS;
119 | DMS_t longitudeDMS;
120 | #endif
121 |
122 | #ifdef GPS_FIX_ALTITUDE
123 | whole_frac alt; // .01 meters
124 |
125 | int32_t altitude_cm() const { return alt.int32_00(); };
126 | float altitude () const { return alt.float_00(); };
127 | float altitude_ft() const { return altitude() * 3.28084; };
128 | #endif
129 |
130 | #ifdef GPS_FIX_VELNED
131 | int32_t velocity_north; // cm/s
132 | int32_t velocity_east; // cm/s
133 | int32_t velocity_down; // cm/s
134 |
135 | void calculateNorthAndEastVelocityFromSpeedAndHeading()
136 | {
137 | #if defined( GPS_FIX_HEADING ) && defined( GPS_FIX_SPEED )
138 | if (valid.heading && valid.speed && valid.velned) {
139 |
140 | float course = heading() * NeoGPS::Location_t::RAD_PER_DEG;
141 | float speed_cm_per_s = speed_metersph() * (100.0 / 3600.0);
142 | velocity_north = round( speed_cm_per_s * cos( course ) );
143 | velocity_east = round( speed_cm_per_s * sin( course ) );
144 | // velocity_down has already been set.
145 |
146 | }
147 | #endif
148 | }
149 | #endif
150 |
151 | #ifdef GPS_FIX_SPEED
152 | whole_frac spd; // .001 nautical miles per hour
153 |
154 | uint32_t speed_mkn () const { return spd.int32_000(); };
155 | float speed () const { return spd.float_000(); };
156 |
157 | // Utilities for speed in other units
158 | CONST_CLASS_DATA float KM_PER_NMI = 1.852;
159 | float speed_kph () const { return speed() * KM_PER_NMI; };
160 |
161 | CONST_CLASS_DATA uint32_t M_PER_NMI = 1852;
162 | uint32_t speed_metersph() const { return (spd.whole * M_PER_NMI) + (spd.frac * M_PER_NMI)/1000; };
163 |
164 | CONST_CLASS_DATA float MI_PER_NMI = 1.150779;
165 | float speed_mph() const { return speed() * MI_PER_NMI; };
166 | #endif
167 |
168 | #ifdef GPS_FIX_HEADING
169 | whole_frac hdg; // .01 degrees
170 |
171 | uint16_t heading_cd() const { return hdg.int16_00(); };
172 | float heading () const { return hdg.float_00(); };
173 | #endif
174 |
175 | //--------------------------------------------------------
176 | // Dilution of Precision is a measure of the current satellite
177 | // constellation geometry WRT how 'good' it is for determining a
178 | // position. This is _independent_ of signal strength and many
179 | // other factors that may be internal to the receiver.
180 | // It _cannot_ be used to determine position accuracy in meters.
181 | // Instead, use the LAT/LON/ALT error in cm members, which are
182 | // populated by GST sentences.
183 |
184 | #ifdef GPS_FIX_HDOP
185 | uint16_t hdop; // Horizontal Dilution of Precision x 1000
186 | #endif
187 | #ifdef GPS_FIX_VDOP
188 | uint16_t vdop; // Vertical Dilution of Precision x 1000
189 | #endif
190 | #ifdef GPS_FIX_PDOP
191 | uint16_t pdop; // Position Dilution of Precision x 1000
192 | #endif
193 |
194 | //--------------------------------------------------------
195 | // Error estimates for lat, lon, altitude, speed, heading and time
196 |
197 | #ifdef GPS_FIX_LAT_ERR
198 | uint16_t lat_err_cm;
199 | float lat_err() const { return lat_err_cm / 100.0; } // m
200 | #endif
201 |
202 | #ifdef GPS_FIX_LON_ERR
203 | uint16_t lon_err_cm;
204 | float lon_err() const { return lon_err_cm / 100.0; } // m
205 | #endif
206 |
207 | #ifdef GPS_FIX_ALT_ERR
208 | uint16_t alt_err_cm;
209 | float alt_err() const { return alt_err_cm / 100.0; } // m
210 | #endif
211 |
212 | #ifdef GPS_FIX_SPD_ERR
213 | uint16_t spd_err_mmps;
214 | float spd_err() const { return spd_err_mmps / 1000.0; } // m/s
215 | #endif
216 |
217 | #ifdef GPS_FIX_HDG_ERR
218 | uint16_t hdg_errE5; // 0.00001 deg
219 | float hdg_err() const { return hdg_errE5 / 1.0e5; } // deg
220 | #endif
221 |
222 | #ifdef GPS_FIX_TIME_ERR
223 | uint16_t time_err_ns;
224 | float time_err() const { return time_err_ns / 1.0e9; } // s
225 | #endif
226 |
227 | //--------------------------------------------------------
228 | // Height of the geoid above the WGS84 ellipsoid
229 | #ifdef GPS_FIX_GEOID_HEIGHT
230 | whole_frac geoidHt; // .01 meters
231 |
232 | int32_t geoidHeight_cm() const { return geoidHt.int32_00(); };
233 | float geoidHeight () const { return geoidHt.float_00(); };
234 | #endif
235 |
236 | //--------------------------------------------------------
237 | // Number of satellites used to calculate a fix.
238 | #ifdef GPS_FIX_SATELLITES
239 | uint8_t satellites;
240 | #endif
241 |
242 | //--------------------------------------------------------
243 | // Date and Time for the fix
244 | #if defined(GPS_FIX_DATE) | defined(GPS_FIX_TIME)
245 | NeoGPS::time_t dateTime ; // Date and Time in one structure
246 | #endif
247 | #if defined(GPS_FIX_TIME)
248 | uint8_t dateTime_cs; // The fix's UTC hundredths of a second
249 | uint32_t dateTime_us() const // The fix's UTC microseconds
250 | { return dateTime_cs * 10000UL; };
251 | uint16_t dateTime_ms() const // The fix's UTC millseconds
252 | { return dateTime_cs * 10; };
253 | #endif
254 |
255 | //--------------------------------------------------------
256 | // The current fix status or mode of the GPS device.
257 | //
258 | // Unfortunately, the NMEA sentences are a little inconsistent
259 | // in their use of "status" and "mode". Both fields are mapped
260 | // onto this enumerated type. Be aware that different
261 | // manufacturers interpret them differently. This can cause
262 | // problems in sentences which include both types (e.g., GPGLL).
263 | //
264 | // Note: Sorted by increasing accuracy. See also /operator |=/.
265 |
266 | enum status_t {
267 | STATUS_NONE,
268 | STATUS_EST,
269 | STATUS_TIME_ONLY,
270 | STATUS_STD,
271 | STATUS_DGPS,
272 | STATUS_RTK_FLOAT,
273 | STATUS_RTK_FIXED,
274 | STATUS_PPS // Precise Position System, *NOT* Pulse-per-second
275 | };
276 |
277 | status_t status NEOGPS_BF(8);
278 |
279 | //--------------------------------------------------------
280 | // Flags to indicate which members of this fix are valid.
281 | //
282 | // Because different sentences contain different pieces of a fix,
283 | // each piece can be valid or NOT valid. If the GPS device does not
284 | // have good reception, some fields may not contain any value.
285 | // Those empty fields will be marked as NOT valid.
286 |
287 | struct valid_t {
288 | bool status NEOGPS_BF(1);
289 |
290 | #if defined(GPS_FIX_DATE)
291 | bool date NEOGPS_BF(1);
292 | #endif
293 |
294 | #if defined(GPS_FIX_TIME)
295 | bool time NEOGPS_BF(1);
296 | #endif
297 |
298 | #if defined( GPS_FIX_LOCATION ) | defined( GPS_FIX_LOCATION_DMS )
299 | bool location NEOGPS_BF(1);
300 | #endif
301 |
302 | #ifdef GPS_FIX_ALTITUDE
303 | bool altitude NEOGPS_BF(1);
304 | #endif
305 |
306 | #ifdef GPS_FIX_SPEED
307 | bool speed NEOGPS_BF(1);
308 | #endif
309 |
310 | #ifdef GPS_FIX_VELNED
311 | bool velned NEOGPS_BF(1);
312 | #endif
313 |
314 | #ifdef GPS_FIX_HEADING
315 | bool heading NEOGPS_BF(1);
316 | #endif
317 |
318 | #ifdef GPS_FIX_SATELLITES
319 | bool satellites NEOGPS_BF(1);
320 | #endif
321 |
322 | #ifdef GPS_FIX_HDOP
323 | bool hdop NEOGPS_BF(1);
324 | #endif
325 | #ifdef GPS_FIX_VDOP
326 | bool vdop NEOGPS_BF(1);
327 | #endif
328 | #ifdef GPS_FIX_PDOP
329 | bool pdop NEOGPS_BF(1);
330 | #endif
331 |
332 | #ifdef GPS_FIX_LAT_ERR
333 | bool lat_err NEOGPS_BF(1);
334 | #endif
335 |
336 | #ifdef GPS_FIX_LON_ERR
337 | bool lon_err NEOGPS_BF(1);
338 | #endif
339 |
340 | #ifdef GPS_FIX_ALT_ERR
341 | bool alt_err NEOGPS_BF(1);
342 | #endif
343 |
344 | #ifdef GPS_FIX_SPD_ERR
345 | bool spd_err NEOGPS_BF(1);
346 | #endif
347 |
348 | #ifdef GPS_FIX_HDG_ERR
349 | bool hdg_err NEOGPS_BF(1);
350 | #endif
351 |
352 | #ifdef GPS_FIX_TIME_ERR
353 | bool time_err NEOGPS_BF(1);
354 | #endif
355 |
356 | #ifdef GPS_FIX_GEOID_HEIGHT
357 | bool geoidHeight NEOGPS_BF(1);
358 | #endif
359 |
360 | // Initialize all flags to false
361 | void init()
362 | {
363 | uint8_t *all = (uint8_t *) this;
364 | for (uint8_t i=0; i.
20 |
21 | /**
22 | * Enable/disable the storage for the members of a fix.
23 | *
24 | * Disabling a member prevents it from being parsed from a received message.
25 | * The disabled member cannot be accessed or stored, and its validity flag
26 | * would not be available. It will not be declared, and code that uses that
27 | * member will not compile.
28 | *
29 | * DATE and TIME are somewhat coupled in that they share a single `time_t`,
30 | * but they have separate validity flags.
31 | *
32 | * See also note regarding the DOP members, below.
33 | *
34 | */
35 |
36 | #define GPS_FIX_DATE
37 | #define GPS_FIX_TIME
38 | #define GPS_FIX_LOCATION
39 | //#define GPS_FIX_LOCATION_DMS
40 | #define GPS_FIX_ALTITUDE
41 | #define GPS_FIX_SPEED
42 | //#define GPS_FIX_VELNED
43 | #define GPS_FIX_HEADING
44 | #define GPS_FIX_SATELLITES
45 | //#define GPS_FIX_HDOP
46 | //#define GPS_FIX_VDOP
47 | //#define GPS_FIX_PDOP
48 | //#define GPS_FIX_LAT_ERR
49 | //#define GPS_FIX_LON_ERR
50 | //#define GPS_FIX_ALT_ERR
51 | //#define GPS_FIX_SPD_ERR
52 | //#define GPS_FIX_HDG_ERR
53 | //#define GPS_FIX_TIME_ERR
54 | //#define GPS_FIX_GEOID_HEIGHT
55 |
56 | #endif
57 |
--------------------------------------------------------------------------------
/NeoGPS/GPSport.h:
--------------------------------------------------------------------------------
1 | #ifndef GPSport_h
2 | #define GPSport_h
3 |
4 | // Copyright (C) 2014-2017, SlashDevin
5 | //
6 | // This file is part of NeoGPS
7 | //
8 | // NeoGPS is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation, either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // NeoGPS is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with NeoGPS. If not, see .
20 |
21 | //-----------------------------------------------------------
22 | // There are 2 ways this file can be used:
23 | //
24 | // I. AS IS, which tries to *guess* which port a beginner should use.
25 | // If you include , , ,
26 | // *OR* before this file (in the INO)
27 | // or on line 152 below, one of those ports will be used for the GPS.
28 | // This file cannot always guess what you want, so you may need to use it
29 | // in the 2nd way...
30 | //
31 | // II. *REPLACE EVERYTHING* in this file to *specify* which port and
32 | // which library you want to use for that port. Just declare the
33 | // port here. You must declare three things:
34 | //
35 | // 1) the "gpsPort" variable (used by all example programs),
36 | // 2) the string for the GPS_PORT_NAME (displayed by all example programs), and
37 | // 3) the DEBUG_PORT to use for Serial Monitor print messages (usually Serial).
38 | //
39 | #define gpsPort Serial
40 | #define GPS_PORT_NAME "Serial"
41 | #define DEBUG_PORT Serial
42 | #endif
43 |
--------------------------------------------------------------------------------
/NeoGPS/Location.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2014-2017, SlashDevin
2 | //
3 | // This file is part of NeoGPS
4 | //
5 | // NeoGPS is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // NeoGPS is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with NeoGPS. If not, see .
17 |
18 | #include "Location.h"
19 |
20 | using namespace NeoGPS;
21 |
22 | //---------------------------------------------------------------------
23 | // Calculate dLon with integers, less one bit to avoid overflow
24 | // (360 * 1e7 = 3600000000, which is too big).
25 | // Retains precision when points are close together.
26 |
27 | int32_t safeDLon( int32_t p2, int32_t p1 )
28 | {
29 | int32_t dLonL;
30 | int32_t halfDLon = (p2/2 - p1/2);
31 | if (halfDLon < -1800000000L/2) {
32 | dLonL = (p2 + 1800000000L) - (p1 - 1800000000L);
33 | } else if (1800000000L/2 < halfDLon) {
34 | dLonL = (p2 - 1800000000L) - (p1 + 1800000000L);
35 | } else {
36 | dLonL = p2 - p1;
37 | }
38 | return dLonL;
39 |
40 | } // safeDLon
41 |
42 | //---------------------------------------------------------------------
43 |
44 | float Location_t::DistanceRadians
45 | ( const Location_t & p1, const Location_t & p2 )
46 | {
47 | int32_t dLonL = safeDLon( p2.lon(), p1.lon() );
48 | int32_t dLatL = p2.lat() - p1.lat();
49 |
50 | if ((abs(dLatL)+abs(dLonL)) < 1000) {
51 | // VERY close together. Just use equirect approximation with precise integers.
52 | // This is not needed for accuracy (that I can measure), but it is
53 | // a quicker calculation.
54 | return EquirectDistanceRadians( p1, p2 );
55 | }
56 |
57 | // Haversine calculation from http://www.movable-type.co.uk/scripts/latlong.html
58 | float dLat = dLatL * RAD_PER_DEG * LOC_SCALE;
59 | float haverDLat = sin(dLat/2.0);
60 | haverDLat *= haverDLat; // squared
61 |
62 | float dLon = dLonL * RAD_PER_DEG * LOC_SCALE;
63 | float haverDLon = sin(dLon/2.0);
64 | haverDLon *= haverDLon; // squared
65 |
66 | float lat1 = p1.latF();
67 | lat1 *= RAD_PER_DEG;
68 | float lat2 = p2.latF();
69 | lat2 *= RAD_PER_DEG;
70 | float a = haverDLat + cos(lat1) * cos(lat2) * haverDLon;
71 |
72 | float c = 2 * atan2( sqrt(a), sqrt(1-a) );
73 |
74 | return c;
75 |
76 | } // DistanceRadians
77 |
78 | //---------------------------------------------------------------------
79 |
80 | float Location_t::EquirectDistanceRadians
81 | ( const Location_t & p1, const Location_t & p2 )
82 | {
83 | // Equirectangular calculation from http://www.movable-type.co.uk/scripts/latlong.html
84 |
85 | float dLat = (p2.lat() - p1.lat()) * RAD_PER_DEG * LOC_SCALE;
86 | float dLon = safeDLon( p2.lon(), p1.lon() ) * RAD_PER_DEG * LOC_SCALE;
87 | float x = dLon * cos( p1.lat() * RAD_PER_DEG * LOC_SCALE + dLat/2 );
88 | return sqrt( x*x + dLat*dLat );
89 |
90 | } // EquirectDistanceRadians
91 |
92 | //---------------------------------------------------------------------
93 |
94 | float Location_t::BearingTo( const Location_t & p1, const Location_t & p2 )
95 | {
96 | int32_t dLonL = safeDLon( p2.lon(), p1.lon() );
97 | float dLon = dLonL * RAD_PER_DEG * LOC_SCALE;
98 | int32_t dLatL = p2.lat() - p1.lat();
99 | float lat1 = p1.lat() * RAD_PER_DEG * LOC_SCALE;
100 | float cosLat1 = cos( lat1 );
101 | float x, y, bearing;
102 |
103 | if ((abs(dLatL)+abs(dLonL)) < 1000) {
104 | // VERY close together. Just use equirect approximation with precise integers.
105 | x = dLonL * cosLat1;
106 | y = dLatL;
107 | bearing = PI/2.0 - atan2(y, x);
108 |
109 | } else {
110 | float lat2 = p2.lat() * RAD_PER_DEG * LOC_SCALE;
111 | float cosLat2 = cos(lat2);
112 | y = sin(dLon) * cosLat2;
113 | x = cosLat1 * sin(lat2) - sin(lat1) * cosLat2 * cos(dLon);
114 | bearing = atan2(y, x);
115 | }
116 |
117 | if (bearing < 0.0)
118 | bearing += TWO_PI;
119 |
120 | return bearing;
121 |
122 | } // BearingTo
123 |
124 | //---------------------------------------------------------------------
125 |
126 | void Location_t::OffsetBy( float distR, float bearingR )
127 | {
128 | float lat1 = lat() * RAD_PER_DEG * LOC_SCALE;
129 | float newLat = asin( sin(lat1)*cos(distR) +
130 | cos(lat1)*sin(distR)*cos(bearingR) );
131 | float dLon = atan2( sin(bearingR)*sin(distR)*cos(lat1),
132 | cos(distR)-sin(lat1)*sin(newLat));
133 |
134 | _lat = (newLat / (RAD_PER_DEG * LOC_SCALE));
135 | _lon += (dLon / (RAD_PER_DEG * LOC_SCALE));
136 |
137 | } // OffsetBy
--------------------------------------------------------------------------------
/NeoGPS/Location.h:
--------------------------------------------------------------------------------
1 | #ifndef NEOGPS_LOCATION_H
2 | #define NEOGPS_LOCATION_H
3 |
4 | #include "NeoGPS_cfg.h"
5 |
6 | // Copyright (C) 2014-2017, SlashDevin
7 | //
8 | // This file is part of NeoGPS
9 | //
10 | // NeoGPS is free software: you can redistribute it and/or modify
11 | // it under the terms of the GNU General Public License as published by
12 | // the Free Software Foundation, either version 3 of the License, or
13 | // (at your option) any later version.
14 | //
15 | // NeoGPS is distributed in the hope that it will be useful,
16 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | // GNU General Public License for more details.
19 | //
20 | // You should have received a copy of the GNU General Public License
21 | // along with NeoGPS. If not, see .
22 |
23 | class NMEAGPS;
24 |
25 | namespace NeoGPS {
26 |
27 | class Location_t
28 | {
29 | public:
30 | CONST_CLASS_DATA float LOC_SCALE = 1.0e-7;
31 |
32 | Location_t() {}
33 | Location_t( int32_t lat, int32_t lon )
34 | : _lat(lat), _lon(lon)
35 | {}
36 | Location_t( float lat, float lon )
37 | : _lat(lat / LOC_SCALE), _lon(lon / LOC_SCALE)
38 | {}
39 | Location_t( double lat, double lon )
40 | : _lat(lat / LOC_SCALE), _lon(lon / LOC_SCALE)
41 | {}
42 |
43 | int32_t lat() const { return _lat; };
44 | void lat( int32_t l ) { _lat = l; };
45 | float latF() const { return ((float) lat()) * LOC_SCALE; };
46 | void latF( float v ) { _lat = v / LOC_SCALE; };
47 |
48 | int32_t lon() const { return _lon; };
49 | void lon( int32_t l ) { _lon = l; };
50 | float lonF() const { return ((float) lon()) * LOC_SCALE; };
51 | void lonF( float v ) { _lon = v / LOC_SCALE; };
52 |
53 | void init() { _lat = _lon = 0; };
54 |
55 | CONST_CLASS_DATA float EARTH_RADIUS_KM = 6371.0088;
56 | CONST_CLASS_DATA float RAD_PER_DEG = PI / 180.0;
57 | CONST_CLASS_DATA float DEG_PER_RAD = 180.0 / PI;
58 | CONST_CLASS_DATA float MI_PER_KM = 0.621371;
59 |
60 | //-----------------------------------
61 | // Distance calculations
62 |
63 | static float DistanceKm( const Location_t & p1, const Location_t & p2 )
64 | {
65 | return DistanceRadians( p1, p2 ) * EARTH_RADIUS_KM;
66 | }
67 | float DistanceKm( const Location_t & p2 ) const
68 | { return DistanceKm( *this, p2 ); }
69 |
70 | static float DistanceMiles( const Location_t & p1, const Location_t & p2 )
71 | {
72 | return DistanceRadians( p1, p2 ) * EARTH_RADIUS_KM * MI_PER_KM;
73 | }
74 | float DistanceMiles( const Location_t & p2 ) const
75 | { return DistanceMiles( *this, p2 ); }
76 |
77 | static float DistanceRadians( const Location_t & p1, const Location_t & p2 );
78 | float DistanceRadians( const Location_t & p2 ) const
79 | { return DistanceRadians( *this, p2 ); }
80 |
81 | static float EquirectDistanceRadians
82 | ( const Location_t & p1, const Location_t & p2 );
83 | float EquirectDistanceRadians( const Location_t & p2 ) const
84 | { return EquirectDistanceRadians( *this, p2 ); }
85 |
86 | static float EquirectDistanceKm( const Location_t & p1, const Location_t & p2 )
87 | {
88 | return EquirectDistanceRadians( p1, p2 ) * EARTH_RADIUS_KM;
89 | }
90 | float EquirectDistanceKm( const Location_t & p2 ) const
91 | { return EquirectDistanceKm( *this, p2 ); }
92 |
93 | static float EquirectDistanceMiles( const Location_t & p1, const Location_t & p2 )
94 | {
95 | return EquirectDistanceRadians( p1, p2 ) * EARTH_RADIUS_KM * MI_PER_KM;
96 | }
97 | float EquirectDistanceMiles( const Location_t & p2 ) const
98 | { return EquirectDistanceMiles( *this, p2 ); }
99 |
100 | //-----------------------------------
101 | // Bearing calculations
102 |
103 | static float BearingTo( const Location_t & p1, const Location_t & p2 ); // radians
104 | float BearingTo( const Location_t & p2 ) const // radians
105 | { return BearingTo( *this, p2 ); }
106 |
107 | static float BearingToDegrees( const Location_t & p1, const Location_t & p2 )
108 | { return BearingTo( p1, p2 ) * DEG_PER_RAD; }
109 | float BearingToDegrees( const Location_t & p2 ) const // radians
110 | { return BearingToDegrees( *this, p2 ); }
111 |
112 | //-----------------------------------
113 | // Offset a location (note distance is in radians, not degrees)
114 | void OffsetBy( float distR, float bearingR );
115 |
116 | //private: //---------------------------------------
117 | friend class NMEAGPS; // This does not work?!?
118 |
119 | int32_t _lat; // degrees * 1e7, negative is South
120 | int32_t _lon; // degrees * 1e7, negative is West
121 |
122 | } NEOGPS_PACKED;
123 |
124 | } // NeoGPS
125 |
126 | #endif
--------------------------------------------------------------------------------
/NeoGPS/NMEAGPS.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2014-2017, SlashDevin
2 | //
3 | // This file is part of NeoGPS
4 | //
5 | // NeoGPS is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // NeoGPS is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with NeoGPS. If not, see .
17 |
18 | #include "NMEAGPS.h"
19 |
20 | #include
21 |
22 | // Check configurations
23 |
24 | #if defined( GPS_FIX_LOCATION_DMS ) & \
25 | !defined( NMEAGPS_PARSING_SCRATCHPAD )
26 |
27 | // The fractional part of the NMEA minutes can have 5 significant figures.
28 | // This requires more temporary storage than is available in the DMS_t.
29 | #error You must enable NMEAGPS_PARSING_SCRATCHPAD in NMEAGPS_cfg.h when GPS_FIX_LOCATION_DMS is enabled in GPSfix_cfg.h!
30 |
31 | #endif
32 |
33 | #ifndef CR
34 | #define CR ((char)13)
35 | #endif
36 | #ifndef LF
37 | #define LF ((char)10)
38 | #endif
39 |
40 | //----------------------------------------------------------------
41 | // Parse a single character as HEX and returns byte value.
42 |
43 | inline static uint8_t parseHEX(char a)
44 | {
45 | a |= 0x20; // make it lowercase
46 | if (('a' <= a) && (a <= 'f'))
47 | return a - 'a' + 10;
48 | else
49 | return a - '0';
50 | }
51 |
52 | //----------------------------------------------------------------
53 | // Format lower nybble of value as HEX and returns character.
54 |
55 | static char formatHex( uint8_t val )
56 | {
57 | val &= 0x0F;
58 | return (val >= 10) ? ((val - 10) + 'A') : (val + '0');
59 | }
60 |
61 | //----------------------------------------------------------------
62 |
63 | inline uint8_t to_binary(uint8_t value)
64 | {
65 | uint8_t high = (value >> 4);
66 | uint8_t low = (value & 0x0f);
67 | return ((high << 3) + (high << 1) + low);
68 | }
69 |
70 | //----------------------------------------------------------------
71 |
72 | NMEAGPS::NMEAGPS()
73 | {
74 | #ifdef NMEAGPS_STATS
75 | statistics.init();
76 | #endif
77 |
78 | data_init();
79 |
80 | reset();
81 | }
82 |
83 | //----------------------------------------------------------------
84 | // Prepare internal members to receive data from sentence fields.
85 |
86 | void NMEAGPS::sentenceBegin()
87 | {
88 | if (intervalComplete()) {
89 | // GPS quiet time is over, this is the start of a new interval.
90 |
91 | #if defined(NMEAGPS_TIMESTAMP_FROM_INTERVAL) & \
92 | ( defined(GPS_FIX_DATE) | defined(GPS_FIX_TIME) )
93 | _IntervalStart = micros();
94 | #endif
95 |
96 | intervalComplete( false );
97 |
98 | #ifdef NMEAGPS_PARSE_SATELLITES
99 | sat_count = 0;
100 | #endif
101 | }
102 |
103 | crc = 0;
104 | nmeaMessage = NMEA_UNKNOWN;
105 | rxState = NMEA_RECEIVING_HEADER;
106 | chrCount = 0;
107 | comma_needed( false );
108 |
109 | #ifdef NMEAGPS_PARSE_PROPRIETARY
110 | proprietary = false;
111 |
112 | #ifdef NMEAGPS_SAVE_MFR_ID
113 | mfr_id[0] =
114 | mfr_id[1] =
115 | mfr_id[2] = 0;
116 | #endif
117 | #endif
118 |
119 | #ifdef NMEAGPS_SAVE_TALKER_ID
120 | talker_id[0] =
121 | talker_id[1] = 0;
122 | #endif
123 |
124 | } // sentenceBegin
125 |
126 | //----------------------------------------------------------------
127 | // All fields from a sentence have been parsed.
128 |
129 | void NMEAGPS::sentenceOk()
130 | {
131 | // Terminate the last field with a comma if the parser needs it.
132 | if (comma_needed()) {
133 | comma_needed( false );
134 | chrCount++;
135 | parseField(',');
136 | }
137 |
138 | #ifdef NMEAGPS_STATS
139 | statistics.ok++;
140 | #endif
141 |
142 | // This implements coherency.
143 | intervalComplete( intervalCompleted() );
144 |
145 | if (intervalComplete()) {
146 | // GPS quiet time now
147 |
148 | }
149 |
150 | reset();
151 | }
152 |
153 | //----------------------------------------------------------------
154 | // There was something wrong with the sentence.
155 |
156 | void NMEAGPS::sentenceInvalid()
157 | {
158 | // All the values are suspect. Start over.
159 | m_fix.valid.init();
160 | nmeaMessage = NMEA_UNKNOWN;
161 |
162 | reset();
163 | }
164 |
165 | //----------------------------------------------------------------
166 | // The sentence is well-formed, but is an unrecognized type
167 |
168 | void NMEAGPS::sentenceUnrecognized()
169 | {
170 | nmeaMessage = NMEA_UNKNOWN;
171 |
172 | reset();
173 | }
174 |
175 | //----------------------------------------------------------------
176 |
177 | void NMEAGPS::headerReceived()
178 | {
179 | NMEAGPS_INIT_FIX(m_fix);
180 | fieldIndex = 1;
181 | chrCount = 0;
182 | rxState = NMEA_RECEIVING_DATA;
183 | }
184 |
185 | //----------------------------------------------------------------
186 | // Process one character of an NMEA GPS sentence.
187 |
188 | NMEAGPS::decode_t NMEAGPS::decode( char c )
189 | {
190 | #ifdef NMEAGPS_STATS
191 | statistics.chars++;
192 | #endif
193 |
194 | decode_t res = DECODE_CHR_OK;
195 |
196 | if (c == '$') { // Always restarts
197 | sentenceBegin();
198 |
199 | } else if (rxState == NMEA_RECEIVING_DATA) { //---------------------------
200 | // Receive complete sentence
201 |
202 | if (c == '*') { // Line finished, CRC follows
203 | rxState = NMEA_RECEIVING_CRC;
204 | chrCount = 0;
205 |
206 | } else if ((' ' <= c) && (c <= '~')) { // Normal data character
207 |
208 | crc ^= c; // accumulate CRC as the chars come in...
209 |
210 | if (!parseField( c ))
211 | sentenceInvalid();
212 | else if (c == ',') {
213 | // Start the next field
214 | comma_needed( false );
215 | fieldIndex++;
216 | chrCount = 0;
217 | } else
218 | chrCount++;
219 |
220 | // This is an undocumented option. It could be useful
221 | // for testing, but all real devices will output a CS.
222 | #ifdef NMEAGPS_CS_OPTIONAL
223 | } else if ((c == CR) || (c == LF)) { // Line finished, no CRC
224 | sentenceOk();
225 | res = DECODE_COMPLETED;
226 | #endif
227 |
228 | } else { // Invalid char
229 | sentenceInvalid();
230 | res = DECODE_CHR_INVALID;
231 | }
232 |
233 |
234 | } else if (rxState == NMEA_RECEIVING_HEADER) { //------------------------
235 |
236 | // The first field is the sentence type. It will be used
237 | // later by the virtual /parseField/.
238 |
239 | crc ^= c; // accumulate CRC as the chars come in...
240 |
241 | decode_t cmd_res = parseCommand( c );
242 |
243 | if (cmd_res == DECODE_CHR_OK) {
244 | chrCount++;
245 | } else if (cmd_res == DECODE_COMPLETED) {
246 | headerReceived();
247 | } else { // DECODE_CHR_INVALID
248 | sentenceUnrecognized();
249 | }
250 |
251 |
252 | } else if (rxState == NMEA_RECEIVING_CRC) { //---------------------------
253 | bool err;
254 | uint8_t nybble = parseHEX( c );
255 |
256 | if (chrCount == 0) {
257 | chrCount++;
258 | err = ((crc >> 4) != nybble);
259 | } else { // chrCount == 1
260 | err = ((crc & 0x0F) != nybble);
261 | if (!err) {
262 | sentenceOk();
263 | res = DECODE_COMPLETED;
264 | }
265 | }
266 |
267 | if (err) {
268 | #ifdef NMEAGPS_STATS
269 | statistics.errors++;
270 | #endif
271 | sentenceInvalid();
272 | }
273 |
274 | } else if (rxState == NMEA_IDLE) { //---------------------------
275 | // Reject non-start characters
276 |
277 | res = DECODE_CHR_INVALID;
278 | nmeaMessage = NMEA_UNKNOWN;
279 | }
280 |
281 | return res;
282 |
283 | } // decode
284 |
285 | //----------------------------------------------------------------
286 |
287 | NMEAGPS::decode_t NMEAGPS::handle( uint8_t c )
288 | {
289 | decode_t res = decode( c );
290 |
291 | if (res == DECODE_COMPLETED) {
292 | storeFix();
293 |
294 | } else if ((NMEAGPS_FIX_MAX == 0) && _available() && !is_safe()) {
295 | // No buffer, and m_fix is was modified by the last char
296 | overrun( true );
297 | }
298 |
299 | return res;
300 |
301 | } // handle
302 |
303 | //----------------------------------------------------------------
304 |
305 | void NMEAGPS::storeFix()
306 | {
307 | // Room for another fix?
308 |
309 | bool room = ((NMEAGPS_FIX_MAX == 0) && !_available()) ||
310 | ((NMEAGPS_FIX_MAX > 0) && (_available() < NMEAGPS_FIX_MAX));
311 |
312 | if (!room) {
313 | overrun( true );
314 |
315 | if (keepNewestFixes) {
316 |
317 | #if NMEAGPS_FIX_MAX > 0
318 |
319 | // Write over the oldest fix (_firstFix), so "pop" it off the front.
320 | _firstFix++;
321 | if (_firstFix >= NMEAGPS_FIX_MAX)
322 | _firstFix = 0;
323 |
324 | // this new one is not available until the interval is complete
325 | _fixesAvailable--;
326 |
327 | #else
328 | // Write over the one and only fix. It may not be complete.
329 | _fixesAvailable = false;
330 | #endif
331 |
332 | // Now there's room!
333 | room = true;
334 | }
335 | }
336 |
337 | if (room) {
338 | // YES, save it.
339 | // Note: If FIX_MAX == 0, this just marks _fixesAvailable = true.
340 |
341 | #if NMEAGPS_FIX_MAX > 0
342 | if (merging == EXPLICIT_MERGING) {
343 | // Accumulate all sentences
344 | buffer[ _currentFix ] |= fix();
345 | }
346 | #endif
347 |
348 | if ((merging == NO_MERGING) || intervalComplete()) {
349 |
350 | #if defined(NMEAGPS_TIMESTAMP_FROM_INTERVAL) & \
351 | defined(GPS_FIX_TIME)
352 |
353 | // If this new fix is the start of a second, save the
354 | // interval start time as the start of this UTC second.
355 |
356 | #if NMEAGPS_FIX_MAX > 0
357 | gps_fix & currentFix = buffer[ _currentFix ];
358 | #else
359 | gps_fix & currentFix = m_fix;
360 | #endif
361 |
362 | if (currentFix.valid.time && (currentFix.dateTime_cs == 0))
363 | UTCsecondStart( _IntervalStart );
364 |
365 | #endif
366 |
367 | #if NMEAGPS_FIX_MAX > 0
368 |
369 | if (merging != EXPLICIT_MERGING)
370 | buffer[ _currentFix ] = fix();
371 |
372 | _currentFix++;
373 | if (_currentFix >= NMEAGPS_FIX_MAX)
374 | _currentFix = 0;
375 |
376 | if (_fixesAvailable < NMEAGPS_FIX_MAX)
377 | _fixesAvailable++;
378 |
379 | #else // FIX_MAX == 0
380 | _fixesAvailable = true;
381 | #endif
382 |
383 | }
384 | }
385 |
386 | } // storeFix
387 |
388 | //----------------------------------------------------------------
389 | // NMEA Sentence strings (alphabetical)
390 |
391 | #if defined(NMEAGPS_PARSE_GGA) | defined(NMEAGPS_RECOGNIZE_ALL)
392 | static const char gga[] __PROGMEM = "GGA";
393 | #endif
394 | #if defined(NMEAGPS_PARSE_GLL) | defined(NMEAGPS_RECOGNIZE_ALL)
395 | static const char gll[] __PROGMEM = "GLL";
396 | #endif
397 | #if defined(NMEAGPS_PARSE_GSA) | defined(NMEAGPS_RECOGNIZE_ALL)
398 | static const char gsa[] __PROGMEM = "GSA";
399 | #endif
400 | #if defined(NMEAGPS_PARSE_GST) | defined(NMEAGPS_RECOGNIZE_ALL)
401 | static const char gst[] __PROGMEM = "GST";
402 | #endif
403 | #if defined(NMEAGPS_PARSE_GSV) | defined(NMEAGPS_RECOGNIZE_ALL)
404 | static const char gsv[] __PROGMEM = "GSV";
405 | #endif
406 | #if defined(NMEAGPS_PARSE_RMC) | defined(NMEAGPS_RECOGNIZE_ALL)
407 | static const char rmc[] __PROGMEM = "RMC";
408 | #endif
409 | #if defined(NMEAGPS_PARSE_VTG) | defined(NMEAGPS_RECOGNIZE_ALL)
410 | static const char vtg[] __PROGMEM = "VTG";
411 | #endif
412 | #if defined(NMEAGPS_PARSE_ZDA) | defined(NMEAGPS_RECOGNIZE_ALL)
413 | static const char zda[] __PROGMEM = "ZDA";
414 | #endif
415 |
416 | static const char * const std_nmea[] __PROGMEM =
417 | {
418 | #if defined(NMEAGPS_PARSE_GGA) | defined(NMEAGPS_RECOGNIZE_ALL)
419 | gga,
420 | #endif
421 | #if defined(NMEAGPS_PARSE_GLL) | defined(NMEAGPS_RECOGNIZE_ALL)
422 | gll,
423 | #endif
424 | #if defined(NMEAGPS_PARSE_GSA) | defined(NMEAGPS_RECOGNIZE_ALL)
425 | gsa,
426 | #endif
427 | #if defined(NMEAGPS_PARSE_GST) | defined(NMEAGPS_RECOGNIZE_ALL)
428 | gst,
429 | #endif
430 | #if defined(NMEAGPS_PARSE_GSV) | defined(NMEAGPS_RECOGNIZE_ALL)
431 | gsv,
432 | #endif
433 | #if defined(NMEAGPS_PARSE_RMC) | defined(NMEAGPS_RECOGNIZE_ALL)
434 | rmc,
435 | #endif
436 | #if defined(NMEAGPS_PARSE_VTG) | defined(NMEAGPS_RECOGNIZE_ALL)
437 | vtg,
438 | #endif
439 | #if defined(NMEAGPS_PARSE_ZDA) | defined(NMEAGPS_RECOGNIZE_ALL)
440 | zda
441 | #endif
442 | };
443 |
444 | // If you change this message table structure, be sure to update the optimizations in
445 | // ::string_for and ::parseCommand for !defined( NMEAGPS_DERIVED_TYPES )
446 |
447 | const NMEAGPS::msg_table_t NMEAGPS::nmea_msg_table __PROGMEM =
448 | {
449 | NMEAGPS::NMEA_FIRST_MSG,
450 | (const msg_table_t *) NULL,
451 | sizeof(std_nmea)/sizeof(std_nmea[0]),
452 | std_nmea
453 | };
454 |
455 | //----------------------------------------------------------------
456 | // For NMEA, start with talker or manufacture ID
457 |
458 | NMEAGPS::decode_t NMEAGPS::parseCommand( char c )
459 | {
460 | if (c == ',') {
461 | // End of field, did we get a sentence type yet?
462 | return
463 | (nmeaMessage == NMEA_UNKNOWN) ?
464 | DECODE_CHR_INVALID :
465 | DECODE_COMPLETED;
466 | }
467 |
468 | #ifdef NMEAGPS_PARSE_PROPRIETARY
469 | if ((chrCount == 0) && (c == 'P')) {
470 | // Starting a proprietary message...
471 | proprietary = true;
472 | return DECODE_CHR_OK;
473 | }
474 | #endif
475 |
476 | uint8_t cmdCount = chrCount;
477 |
478 | #ifdef NMEAGPS_PARSE_PROPRIETARY
479 | if (proprietary) {
480 |
481 | // Next three chars are the manufacturer ID
482 | if (chrCount < 4) {
483 | #ifdef NMEAGPS_SAVE_MFR_ID
484 | mfr_id[chrCount-1] = c;
485 | #endif
486 |
487 | #ifdef NMEAGPS_PARSE_MFR_ID
488 | if (!parseMfrID( c ))
489 | return DECODE_CHR_INVALID;
490 | #endif
491 |
492 | return DECODE_CHR_OK;
493 | }
494 |
495 | cmdCount -= 4;
496 |
497 | } else
498 | #endif
499 | { // standard
500 |
501 | // First two chars are talker ID
502 | if (chrCount < 2) {
503 | #ifdef NMEAGPS_SAVE_TALKER_ID
504 | talker_id[chrCount] = c;
505 | #endif
506 |
507 | #ifdef NMEAGPS_PARSE_TALKER_ID
508 | if (!parseTalkerID( c ))
509 | return DECODE_CHR_INVALID;
510 | #endif
511 |
512 | return DECODE_CHR_OK;
513 | }
514 |
515 | cmdCount -= 2;
516 | }
517 |
518 | // The remaining characters are the message type.
519 |
520 | const msg_table_t *msgs = msg_table();
521 |
522 | return parseCommand( msgs, cmdCount, c );
523 |
524 | } // parseCommand
525 |
526 | //----------------------------------------------------------------
527 | // Determine the NMEA sentence type
528 |
529 | NMEAGPS::decode_t NMEAGPS::parseCommand
530 | ( const msg_table_t *msgs, uint8_t cmdCount, char c )
531 | {
532 | for (;;) {
533 | #ifdef NMEAGPS_DERIVED_TYPES
534 | uint8_t table_size = pgm_read_byte( &msgs->size );
535 | uint8_t msg_offset = pgm_read_byte( &msgs->offset );
536 | bool check_this_table = true;
537 | #else
538 | const uint8_t table_size = sizeof(std_nmea)/sizeof(std_nmea[0]);
539 | const uint8_t msg_offset = NMEA_FIRST_MSG;
540 | const bool check_this_table = true;
541 | #endif
542 | decode_t res = DECODE_CHR_INVALID;
543 | uint8_t entry;
544 |
545 | if (nmeaMessage == NMEA_UNKNOWN) {
546 | // We're just starting
547 | entry = 0;
548 |
549 | } else if ((msg_offset <= nmeaMessage) && (nmeaMessage < msg_offset+table_size)) {
550 | // In range of this table, pick up where we left off
551 | entry = nmeaMessage - msg_offset;
552 | }
553 | #ifdef NMEAGPS_DERIVED_TYPES
554 | else
555 | check_this_table = false;
556 | #endif
557 |
558 | if (check_this_table) {
559 | uint8_t i = entry;
560 |
561 | #if !defined( NMEAGPS_DERIVED_TYPES )
562 | const char * const *table = std_nmea;
563 | #else
564 | const char * const *table = (const char * const *) pgm_read_ptr( &msgs->table );
565 | #endif
566 | const char * table_i = (const char * ) pgm_read_ptr( &table[i] );
567 |
568 | for (;;) {
569 | char rc = pgm_read_byte( &table_i[cmdCount] );
570 | if (c == rc) {
571 | // ok so far...
572 | entry = i;
573 | res = DECODE_CHR_OK;
574 | break;
575 | }
576 |
577 | if (c < rc) {
578 | // Alphabetical rejection, check next table
579 | break;
580 | }
581 |
582 | // Ok to check another entry in this table
583 | uint8_t next_msg = i+1;
584 | if (next_msg >= table_size) {
585 | // No more entries in this table.
586 | break;
587 | }
588 |
589 | // See if the next entry starts with the same characters.
590 | const char *table_next = (const char *) pgm_read_ptr( &table[next_msg] );
591 |
592 | for (uint8_t j = 0; j < cmdCount; j++)
593 | if (pgm_read_byte( &table_i[j] ) != pgm_read_byte( &table_next[j] )) {
594 | // Nope, a different start to this entry
595 | break;
596 | }
597 | i = next_msg;
598 | table_i = table_next;
599 | }
600 | }
601 |
602 | if (res == DECODE_CHR_INVALID) {
603 |
604 | #ifdef NMEAGPS_DERIVED_TYPES
605 | msgs = (const msg_table_t *) pgm_read_ptr( &msgs->previous );
606 | if (msgs) {
607 | // Try the current character in the previous table
608 | continue;
609 | } // else
610 | // No more tables, chr is invalid.
611 | #endif
612 |
613 | } else {
614 | // This entry is good so far.
615 | nmeaMessage = (nmea_msg_t) (entry + msg_offset);
616 | }
617 |
618 | return res;
619 | }
620 |
621 | } // parseCommand
622 |
623 | //----------------------------------------------------------------
624 |
625 | const __FlashStringHelper *NMEAGPS::string_for( nmea_msg_t msg ) const
626 | {
627 | if (msg == NMEA_UNKNOWN)
628 | return F("UNK");
629 |
630 | #ifdef NMEAGPS_DERIVED_TYPES
631 | const msg_table_t *msgs = msg_table();
632 | #endif
633 |
634 | for (;;) {
635 | #ifdef NMEAGPS_DERIVED_TYPES
636 | uint8_t table_size = pgm_read_byte( &msgs->size );
637 | uint8_t msg_offset = pgm_read_byte( &msgs->offset );
638 | #else
639 | const uint8_t table_size = sizeof(std_nmea)/sizeof(std_nmea[0]);
640 | const uint8_t msg_offset = NMEA_FIRST_MSG;
641 | #endif
642 |
643 | if ((msg_offset <= msg) && (msg < msg_offset+table_size)) {
644 | // In range of this table
645 | #if !defined( NMEAGPS_DERIVED_TYPES )
646 | const char * const *table = std_nmea;
647 | #else
648 | const char * const *table = (const char * const *) pgm_read_ptr( &msgs->table );
649 | #endif
650 | const uint8_t i = ((uint8_t)msg) - msg_offset;
651 | const char * table_i = (const char *) pgm_read_ptr( &table[i] );
652 | return (const __FlashStringHelper *) table_i;
653 | }
654 |
655 | #ifdef NMEAGPS_DERIVED_TYPES
656 | // Try the previous table
657 | msgs = (const msg_table_t *) pgm_read_ptr( &msgs->previous );
658 | if (msgs)
659 | continue;
660 | #endif
661 |
662 | return (const __FlashStringHelper *) NULL;
663 | }
664 |
665 | } // string_for
666 |
667 | //----------------------------------------------------------------
668 |
669 | bool NMEAGPS::parseField(char chr)
670 | {
671 | switch (nmeaMessage) {
672 |
673 | #if defined(NMEAGPS_PARSE_GGA)
674 | case NMEA_GGA: return parseGGA( chr );
675 | #endif
676 |
677 | #if defined(NMEAGPS_PARSE_GLL)
678 | case NMEA_GLL: return parseGLL( chr );
679 | #endif
680 |
681 | #if defined(NMEAGPS_PARSE_GSA)
682 | case NMEA_GSA: return parseGSA( chr );
683 | #endif
684 |
685 | #if defined(NMEAGPS_PARSE_GST)
686 | case NMEA_GST: return parseGST( chr );
687 | #endif
688 |
689 | #if defined(NMEAGPS_PARSE_GSV)
690 | case NMEA_GSV: return parseGSV( chr );
691 | #endif
692 |
693 | #if defined(NMEAGPS_PARSE_RMC)
694 | case NMEA_RMC: return parseRMC( chr );
695 | #endif
696 |
697 | #if defined(NMEAGPS_PARSE_VTG)
698 | case NMEA_VTG: return parseVTG( chr );
699 | #endif
700 |
701 | #if defined(NMEAGPS_PARSE_ZDA)
702 | case NMEA_ZDA: return parseZDA( chr );
703 | #endif
704 |
705 | default:
706 | break;
707 | }
708 |
709 | return true;
710 |
711 | } // parseField
712 |
713 | //----------------------------------------------------------------
714 |
715 | bool NMEAGPS::parseGGA( char chr )
716 | {
717 | #ifdef NMEAGPS_PARSE_GGA
718 | switch (fieldIndex) {
719 | case 1: return parseTime( chr );
720 | PARSE_LOC(2);
721 | case 6: return parseFix( chr );
722 | case 7: return parseSatellites( chr );
723 | case 8: return parseHDOP( chr );
724 | case 9: return parseAlt( chr );
725 | case 11: return parseGeoidHeight( chr );
726 | }
727 | #endif
728 |
729 | return true;
730 |
731 | } // parseGGA
732 |
733 | //----------------------------------------------------------------
734 |
735 | bool NMEAGPS::parseGLL( char chr )
736 | {
737 | #ifdef NMEAGPS_PARSE_GLL
738 | switch (fieldIndex) {
739 | PARSE_LOC(1);
740 | case 5: return parseTime( chr );
741 | case 7: return parseFix( chr );
742 | }
743 | #endif
744 |
745 | return true;
746 |
747 | } // parseGLL
748 |
749 | //----------------------------------------------------------------
750 |
751 | bool NMEAGPS::parseGSA( char chr )
752 | {
753 | #ifdef NMEAGPS_PARSE_GSA
754 |
755 | switch (fieldIndex) {
756 | case 2:
757 | if (chrCount == 0) {
758 | if ((chr == '2') || (chr == '3')) {
759 | m_fix.status = gps_fix::STATUS_STD;
760 | m_fix.valid.status = true;
761 | } else if (chr == '1') {
762 | m_fix.status = gps_fix::STATUS_NONE;
763 | m_fix.valid.status = true;
764 | } else if (validateChars() | validateFields())
765 | sentenceInvalid();
766 | }
767 | break;
768 |
769 | case 15:
770 | #if !defined( NMEAGPS_PARSE_GSV )
771 | // Finalize the satellite count (for the fix *and* the satellites array)
772 | if (chrCount == 0) {
773 | if (sat_count >= NMEAGPS_MAX_SATELLITES)
774 | sat_count = NMEAGPS_MAX_SATELLITES-1;
775 | #if defined( NMEAGPS_PARSE_SATELLITES ) && \
776 | !defined( NMEAGPS_PARSE_GGA )
777 | m_fix.valid.satellites = (m_fix.satellites > 0);
778 | #endif
779 | }
780 | #endif
781 | return parsePDOP( chr );
782 |
783 | case 16: return parseHDOP( chr );
784 | case 17: return parseVDOP( chr );
785 | #if defined(GPS_FIX_VDOP) & !defined(NMEAGPS_COMMA_NEEDED)
786 | #error When GSA and VDOP are enabled, you must define NMEAGPS_COMMA_NEEDED in NMEAGPS_cfg.h!
787 | #endif
788 |
789 | #ifdef NMEAGPS_PARSE_SATELLITES
790 |
791 | // It's not clear how this sentence relates to GSV and GGA.
792 | // GSA only allows 12 satellites, while GSV allows any number.
793 | // GGA just says how many are used to calculate a fix.
794 |
795 | // GGA shall have priority over GSA with respect to populating the
796 | // satellites field. Don't use the number of satellite ID fields
797 | // to set the satellites field if GGA is enabled.
798 |
799 | // GSV shall have priority over GSA with respect to populating the
800 | // satellites array. Ignore the satellite ID fields if GSV is enabled.
801 |
802 | case 1: break; // allows "default:" case for SV fields
803 |
804 | #ifndef NMEAGPS_PARSE_GSV
805 | case 3:
806 | if (chrCount == 0) {
807 | sat_count = 0;
808 | #ifndef NMEAGPS_PARSE_GGA
809 | NMEAGPS_INVALIDATE( satellites );
810 | m_fix.satellites = 0;
811 | #endif
812 | comma_needed( true );
813 | }
814 | default:
815 | if (chr == ',') {
816 | if (chrCount > 0) {
817 | sat_count++;
818 | #ifndef NMEAGPS_PARSE_GGA
819 | m_fix.satellites++;
820 | #endif
821 | }
822 | } else if (sat_count < NMEAGPS_MAX_SATELLITES)
823 | parseInt( satellites[ sat_count ].id, chr );
824 | break;
825 | #endif
826 | #endif
827 | }
828 | #endif
829 |
830 | return true;
831 |
832 | } // parseGSA
833 |
834 | //----------------------------------------------------------------
835 |
836 | bool NMEAGPS::parseGST( char chr )
837 | {
838 | #ifdef NMEAGPS_PARSE_GST
839 | switch (fieldIndex) {
840 | case 1: return parseTime( chr );
841 | case 6: return parse_lat_err( chr );
842 | case 7: return parse_lon_err( chr );
843 | case 8: return parse_alt_err( chr );
844 | }
845 | #endif
846 |
847 | return true;
848 |
849 | } // parseGST
850 |
851 | //----------------------------------------------------------------
852 |
853 | bool NMEAGPS::parseGSV( char chr )
854 | {
855 | #if defined(NMEAGPS_PARSE_GSV) & defined(NMEAGPS_PARSE_SATELLITES)
856 | #if !defined(NMEAGPS_PARSE_GSA) & !defined(NMEAGPS_PARSE_GGA)
857 | if ((sat_count == 0) && (fieldIndex == 1) && (chrCount == 0)) {
858 | NMEAGPS_INVALIDATE( satellites );
859 | m_fix.satellites = 0;
860 | }
861 | #endif
862 |
863 | if (sat_count < NMEAGPS_MAX_SATELLITES) {
864 | if (fieldIndex >= 4) {
865 |
866 | switch (fieldIndex % 4) {
867 | #ifdef NMEAGPS_PARSE_SATELLITE_INFO
868 | case 0: parseInt( satellites[sat_count].id , chr ); break;
869 | case 1: parseInt( satellites[sat_count].elevation, chr ); break;
870 | case 2:
871 | if (chr != ',')
872 | parseInt( satellites[sat_count].azimuth, chr );
873 | else {
874 | // field 3 can be omitted, do some things now
875 | satellites[sat_count].tracked = false;
876 | sat_count++;
877 | }
878 | break;
879 | case 3:
880 | if (chr != ',') {
881 | uint8_t snr = satellites[sat_count-1].snr;
882 | parseInt( snr, chr );
883 | satellites[sat_count-1].snr = snr;
884 | comma_needed( true );
885 | } else {
886 | satellites[sat_count-1].tracked = (chrCount != 0);
887 | #if !defined(NMEAGPS_PARSE_GSA) & !defined(NMEAGPS_PARSE_GGA)
888 | if (satellites[sat_count-1].tracked) {
889 | m_fix.satellites++;
890 | m_fix.valid.satellites = true; // but there may be more
891 | }
892 | #endif
893 | }
894 | break;
895 | #else
896 | case 0:
897 | if (chr != ',')
898 | parseInt( satellites[sat_count].id, chr );
899 | else {
900 | sat_count++;
901 | #if !defined(NMEAGPS_PARSE_GSA) & !defined(NMEAGPS_PARSE_GGA)
902 | m_fix.satellites++;
903 | m_fix.valid.satellites = true; // but there may be more
904 | #endif
905 | }
906 | break;
907 | case 3:
908 | #if !defined(NMEAGPS_PARSE_GSA) & !defined(NMEAGPS_PARSE_GGA)
909 | if ((chr == ',') && (chrCount != 0)) {
910 | m_fix.satellites++; // tracked
911 | m_fix.valid.satellites = true; // but there may be more
912 | }
913 | #endif
914 | break;
915 | #endif
916 | }
917 | }
918 | }
919 | #endif
920 |
921 | return true;
922 |
923 | } // parseGSV
924 |
925 | //----------------------------------------------------------------
926 |
927 | bool NMEAGPS::parseRMC( char chr )
928 | {
929 | #ifdef NMEAGPS_PARSE_RMC
930 | switch (fieldIndex) {
931 | case 1: return parseTime ( chr );
932 | case 2: return parseFix ( chr );
933 | PARSE_LOC(3);
934 | case 7: return parseSpeed ( chr );
935 | case 8: return parseHeading( chr );
936 | case 9: return parseDDMMYY ( chr );
937 | // case 12: return parseFix ( chr ); ublox only!
938 | }
939 | #endif
940 |
941 | return true;
942 |
943 | } // parseRMC
944 |
945 | //----------------------------------------------------------------
946 |
947 | bool NMEAGPS::parseVTG( char chr )
948 | {
949 | #ifdef NMEAGPS_PARSE_VTG
950 | switch (fieldIndex) {
951 | case 1: return parseHeading( chr );
952 | case 5: return parseSpeed( chr );
953 | case 9: return parseFix( chr );
954 | }
955 | #endif
956 |
957 | return true;
958 |
959 | } // parseVTG
960 |
961 | //----------------------------------------------------------------
962 |
963 | bool NMEAGPS::parseZDA( char chr )
964 | {
965 | #ifdef NMEAGPS_PARSE_ZDA
966 | switch (fieldIndex) {
967 | case 1: return parseTime( chr );
968 |
969 | #ifdef GPS_FIX_DATE
970 | case 2:
971 | if (chrCount == 0) {
972 | NMEAGPS_INVALIDATE( date );
973 | if (validateFields())
974 | comma_needed( true );
975 | }
976 | parseInt( m_fix.dateTime.date , chr );
977 |
978 | if (validateFields() && (chrCount > 0) && (chr == ',')) {
979 | uint8_t days =
980 | pgm_read_byte
981 | ( &NeoGPS::time_t::days_in[ m_fix.dateTime.date ] );
982 | if ((m_fix.dateTime.date < 1) || (days < m_fix.dateTime.date))
983 | sentenceInvalid();
984 | }
985 | break;
986 |
987 | case 3:
988 | if (validateFields() && (chrCount == 0))
989 | comma_needed( true );
990 |
991 | parseInt( m_fix.dateTime.month, chr );
992 |
993 | if (validateFields() && (chrCount > 0) && (chr == ',') &&
994 | ((m_fix.dateTime.month < 1) || (12 < m_fix.dateTime.month)))
995 | sentenceInvalid();
996 | break;
997 |
998 | case 4:
999 | if (validateFields() && (chrCount == 0))
1000 | comma_needed( true );
1001 |
1002 | if (chr != ',') {
1003 | // year is BCD until terminating comma.
1004 | // This essentially keeps the last two digits
1005 | if (validateChars() && !isdigit( chr ))
1006 | sentenceInvalid();
1007 | else if (chrCount == 0) {
1008 | comma_needed( true );
1009 | m_fix.dateTime.year = (chr - '0');
1010 | } else
1011 | m_fix.dateTime.year = (m_fix.dateTime.year << 4) + (chr - '0');
1012 |
1013 | } else {
1014 | // Terminating comma received, convert from BCD to decimal
1015 | m_fix.dateTime.year = to_binary( m_fix.dateTime.year );
1016 | if (validateFields() &&
1017 | ( ((chrCount != 2) && (chrCount != 4)) ||
1018 | (99 < m_fix.dateTime.year) ))
1019 | sentenceInvalid();
1020 | else
1021 | m_fix.valid.date = true;
1022 | }
1023 | break;
1024 | #endif
1025 | }
1026 | #endif
1027 |
1028 | return true;
1029 |
1030 | } // parseZDA
1031 |
1032 | //----------------------------------------------------------------
1033 |
1034 | bool NMEAGPS::parseTime(char chr)
1035 | {
1036 | #ifdef GPS_FIX_TIME
1037 | switch (chrCount) {
1038 | case 0:
1039 | NMEAGPS_INVALIDATE( time );
1040 | m_fix.dateTime_cs = 0;
1041 |
1042 | if (chr != ',') {
1043 | comma_needed( true );
1044 | if (validateChars() && !isdigit(chr))
1045 | sentenceInvalid();
1046 | else
1047 | m_fix.dateTime.hours = (chr - '0')*10;
1048 | }
1049 | break;
1050 |
1051 | case 1:
1052 | if (validateChars() && !isdigit(chr))
1053 | sentenceInvalid();
1054 | else
1055 | m_fix.dateTime.hours += (chr - '0');
1056 |
1057 | if (validateFields() && (23 < m_fix.dateTime.hours))
1058 | sentenceInvalid();
1059 | break;
1060 |
1061 | case 2:
1062 | if (validateChars() && !isdigit(chr))
1063 | sentenceInvalid();
1064 | else
1065 | m_fix.dateTime.minutes = (chr - '0')*10;
1066 | break;
1067 | case 3:
1068 | if (validateChars() && !isdigit(chr))
1069 | sentenceInvalid();
1070 | else
1071 | m_fix.dateTime.minutes += (chr - '0');
1072 | if (validateFields() && (59 < m_fix.dateTime.minutes))
1073 | sentenceInvalid();
1074 | break;
1075 |
1076 | case 4:
1077 | if (validateChars() && !isdigit(chr))
1078 | sentenceInvalid();
1079 | else
1080 | m_fix.dateTime.seconds = (chr - '0')*10;
1081 | break;
1082 | case 5:
1083 | if (validateChars() && !isdigit(chr))
1084 | sentenceInvalid();
1085 | else
1086 | m_fix.dateTime.seconds += (chr - '0');
1087 | if (validateFields() && (59 < m_fix.dateTime.seconds))
1088 | sentenceInvalid();
1089 | break;
1090 |
1091 | case 6:
1092 | if (chr == ',')
1093 | m_fix.valid.time = true;
1094 | else if (validateChars() && (chr != '.'))
1095 | sentenceInvalid();
1096 | break;
1097 |
1098 | case 7:
1099 | if (chr == ',')
1100 | m_fix.valid.time = true;
1101 | else if (validateChars() && !isdigit(chr))
1102 | sentenceInvalid();
1103 | else
1104 | m_fix.dateTime_cs = (chr - '0')*10;
1105 | break;
1106 | case 8:
1107 | if (chr == ',')
1108 | m_fix.valid.time = true;
1109 | else if (validateChars() && !isdigit(chr))
1110 | sentenceInvalid();
1111 | else {
1112 | m_fix.dateTime_cs += (chr - '0');
1113 | if (validateFields() && (99 < m_fix.dateTime_cs))
1114 | sentenceInvalid();
1115 | else
1116 | m_fix.valid.time = true;
1117 | }
1118 | break;
1119 |
1120 | default:
1121 | if (validateChars() && !isdigit( chr ) && (chr != ','))
1122 | sentenceInvalid();
1123 | break;
1124 | }
1125 | #endif
1126 |
1127 | return true;
1128 |
1129 | } // parseTime
1130 |
1131 | //----------------------------------------------------------------
1132 |
1133 | bool NMEAGPS::parseDDMMYY( char chr )
1134 | {
1135 | #ifdef GPS_FIX_DATE
1136 | switch (chrCount) {
1137 | case 0:
1138 | NMEAGPS_INVALIDATE( date );
1139 |
1140 | if (chr != ',') {
1141 | if (validateChars())
1142 | comma_needed( true );
1143 | if (validateChars() && !isdigit( chr ))
1144 | sentenceInvalid();
1145 | else
1146 | m_fix.dateTime.date = (chr - '0')*10;
1147 | }
1148 | break;
1149 |
1150 | case 1:
1151 | if (validateChars() && !isdigit( chr ))
1152 | sentenceInvalid();
1153 | else {
1154 | m_fix.dateTime.date += (chr - '0');
1155 |
1156 | if (validateFields()) {
1157 | uint8_t days =
1158 | pgm_read_byte
1159 | ( &NeoGPS::time_t::days_in[m_fix.dateTime.date] );
1160 | if ((m_fix.dateTime.date < 1) || (days < m_fix.dateTime.date))
1161 | sentenceInvalid();
1162 | }
1163 | }
1164 | break;
1165 |
1166 | case 2:
1167 | if (validateChars() && !isdigit( chr ))
1168 | sentenceInvalid();
1169 | else
1170 | m_fix.dateTime.month = (chr - '0')*10;
1171 | break;
1172 | case 3:
1173 | if (validateChars() && !isdigit( chr ))
1174 | sentenceInvalid();
1175 | else {
1176 | m_fix.dateTime.month += (chr - '0');
1177 |
1178 | if (validateFields() &&
1179 | ((m_fix.dateTime.month < 1) || (12 < m_fix.dateTime.month)))
1180 | sentenceInvalid();
1181 | }
1182 | break;
1183 |
1184 | case 4:
1185 | if (validateChars() && !isdigit( chr ))
1186 | sentenceInvalid();
1187 | else
1188 | m_fix.dateTime.year = (chr - '0')*10;
1189 | break;
1190 | case 5:
1191 | if (validateChars() && !isdigit( chr ))
1192 | sentenceInvalid();
1193 | else {
1194 | m_fix.dateTime.year += (chr - '0');
1195 | m_fix.valid.date = true;
1196 | }
1197 | break;
1198 |
1199 | case 6:
1200 | if (validateChars() && (chr != ','))
1201 | sentenceInvalid();
1202 | break;
1203 | }
1204 | #endif
1205 |
1206 | return true;
1207 |
1208 | } // parseDDMMYY
1209 |
1210 | //----------------------------------------------------------------
1211 |
1212 | bool NMEAGPS::parseFix( char chr )
1213 | {
1214 | if (chrCount == 0) {
1215 | NMEAGPS_INVALIDATE( status );
1216 | bool ok = true;
1217 | if ((chr == '1') || (chr == 'A'))
1218 | m_fix.status = gps_fix::STATUS_STD;
1219 | else if ((chr == '0') || (chr == 'N') || (chr == 'V'))
1220 | m_fix.status = gps_fix::STATUS_NONE;
1221 | else if ((chr == '2') || (chr == 'D'))
1222 | m_fix.status = gps_fix::STATUS_DGPS;
1223 | else if (chr == '3')
1224 | m_fix.status = gps_fix::STATUS_PPS;
1225 | else if (chr == '4')
1226 | m_fix.status = gps_fix::STATUS_RTK_FIXED;
1227 | else if (chr == '5')
1228 | m_fix.status = gps_fix::STATUS_RTK_FLOAT;
1229 | else if ((chr == '6') || (chr == 'E'))
1230 | m_fix.status = gps_fix::STATUS_EST;
1231 | else {
1232 | if (validateChars() | validateFields()) {
1233 | sentenceInvalid();
1234 | }
1235 | ok = false;
1236 | }
1237 | if (ok)
1238 | m_fix.valid.status = true;
1239 | } if ((validateChars() | validateFields()) && ((chrCount > 1) || (chr != ','))) {
1240 | sentenceInvalid();
1241 | }
1242 |
1243 | return true;
1244 |
1245 | } // parseFix
1246 |
1247 | //----------------------------------------------------------------
1248 |
1249 | bool NMEAGPS::parseFloat
1250 | ( gps_fix::whole_frac & val, char chr, uint8_t max_decimal )
1251 | {
1252 | bool done = false;
1253 |
1254 | if (chrCount == 0) {
1255 | val.init();
1256 | comma_needed( true );
1257 | decimal = 0;
1258 | negative = (chr == '-');
1259 | if (negative) return done;
1260 | }
1261 |
1262 | if (chr == ',') {
1263 | // End of field, make sure it's scaled up
1264 | if (!decimal)
1265 | decimal = 1;
1266 | if (val.frac)
1267 | while (decimal++ <= max_decimal)
1268 | val.frac *= 10;
1269 | if (negative) {
1270 | val.frac = -val.frac;
1271 | val.whole = -val.whole;
1272 | }
1273 | done = true;
1274 | } else if (chr == '.') {
1275 | decimal = 1;
1276 | } else if (validateChars() && !isdigit(chr)) {
1277 | sentenceInvalid();
1278 | } else if (!decimal) {
1279 | val.whole = val.whole*10 + (chr - '0');
1280 | } else if (decimal++ <= max_decimal) {
1281 | val.frac = val.frac*10 + (chr - '0');
1282 | }
1283 |
1284 | return done;
1285 |
1286 | } // parseFloat
1287 |
1288 | //----------------------------------------------------------------
1289 |
1290 | bool NMEAGPS::parseFloat( uint16_t & val, char chr, uint8_t max_decimal )
1291 | {
1292 | bool done = false;
1293 |
1294 | if (chrCount == 0) {
1295 | val = 0;
1296 | comma_needed( true );
1297 | decimal = 0;
1298 | negative = (chr == '-');
1299 | if (negative) return done;
1300 | }
1301 |
1302 | if (chr == ',') {
1303 | if (val) {
1304 | if (!decimal)
1305 | decimal = 1;
1306 | while (decimal++ <= max_decimal) {
1307 | if (validateFields() && (val > 6553)) // 65535/10
1308 | sentenceInvalid();
1309 | else
1310 | val *= 10;
1311 | }
1312 | if (negative)
1313 | val = -val;
1314 | }
1315 | done = true;
1316 | } else if (chr == '.') {
1317 | decimal = 1;
1318 | } else if (validateChars() && !isdigit(chr)) {
1319 | sentenceInvalid();
1320 | } else if (!decimal || (decimal++ <= max_decimal)) {
1321 | if (validateFields() &&
1322 | ((val > 6553) || ((val == 6553) && (chr > '5'))))
1323 | sentenceInvalid();
1324 | else
1325 | val = val*10 + (chr - '0');
1326 | }
1327 |
1328 | return done;
1329 |
1330 | } // parseFloat
1331 |
1332 | //----------------------------------------------------------------
1333 |
1334 | #ifdef GPS_FIX_LOCATION_DMS
1335 |
1336 | static void finalizeDMS( uint32_t min_frac, DMS_t & dms )
1337 | {
1338 | // To convert from fractional minutes (hundred thousandths) to
1339 | // seconds_whole and seconds_frac,
1340 | //
1341 | // seconds = min_frac * 60/100000
1342 | // = min_frac * 0.0006
1343 |
1344 | #ifdef __AVR__
1345 | // Fixed point conversion factor 0.0006 * 2^26 = 40265
1346 | const uint32_t to_seconds_E26 = 40265UL;
1347 |
1348 | uint32_t secs_E26 = min_frac * to_seconds_E26;
1349 | uint8_t secs = secs_E26 >> 26;
1350 | uint32_t remainder_E26 = secs_E26 - (((uint32_t) secs) << 26);
1351 |
1352 | // thousandths = (rem_E26 * 1000) >> 26;
1353 | // = (rem_E26 * 125) >> 23; // 1000 = (125 << 3)
1354 | // = ((rem_E26 >> 8) * 125) >> 15; // avoid overflow
1355 | // = (((rem_E26 >> 8) * 125) >> 8) >> 7; // final shift 15 in two steps
1356 | // = ((((rem_E26 >> 8) * 125) >> 8) + 72) >> 7;
1357 | // // round up
1358 | uint16_t frac_x1000 = ((((remainder_E26 >> 8) * 125UL) >> 8) + 64) >> 7;
1359 |
1360 | #else // not __AVR__
1361 |
1362 | min_frac *= 6UL;
1363 | uint32_t secs = min_frac / 10000UL;
1364 | uint16_t frac_x1000 = (min_frac - (secs * 10000UL) + 5) / 10;
1365 |
1366 | #endif
1367 |
1368 | // Rounding up can yield a frac of 1000/1000ths. Carry into whole.
1369 | if (frac_x1000 >= 1000) {
1370 | frac_x1000 -= 1000;
1371 | secs += 1;
1372 | }
1373 | dms.seconds_whole = secs;
1374 | dms.seconds_frac = frac_x1000;
1375 |
1376 | } // finalizeDMS
1377 |
1378 | #endif
1379 |
1380 | //.................................................
1381 | // From http://www.hackersdelight.org/divcMore.pdf
1382 |
1383 | static uint32_t divu3( uint32_t n )
1384 | {
1385 | #ifdef __AVR__
1386 | uint32_t q = (n >> 2) + (n >> 4); // q = n*0.0101 (approx).
1387 | q = q + (q >> 4); // q = n*0.01010101.
1388 | q = q + (q >> 8);
1389 | q = q + (q >> 16);
1390 |
1391 | uint32_t r = n - q*3; // 0 <= r <= 15.
1392 | return q + (11*r >> 5); // Returning q + r/3.
1393 | #else
1394 | return n/3;
1395 | #endif
1396 | }
1397 |
1398 | //.................................................
1399 | // Parse lat/lon dddmm.mmmm fields
1400 |
1401 | bool NMEAGPS::parseDDDMM
1402 | (
1403 | #if defined( GPS_FIX_LOCATION )
1404 | int32_t & val,
1405 | #endif
1406 | #if defined( GPS_FIX_LOCATION_DMS )
1407 | DMS_t & dms,
1408 | #endif
1409 | char chr
1410 | )
1411 | {
1412 | bool done = false;
1413 |
1414 | #if defined( GPS_FIX_LOCATION ) | defined( GPS_FIX_LOCATION_DMS )
1415 |
1416 | if (chrCount == 0) {
1417 | #ifdef GPS_FIX_LOCATION
1418 | val = 0;
1419 | #endif
1420 | #ifdef GPS_FIX_LOCATION_DMS
1421 | dms.init();
1422 | #endif
1423 | decimal = 0;
1424 | comma_needed( true );
1425 | }
1426 |
1427 | if ((chr == '.') || ((chr == ',') && !decimal)) {
1428 | // Now we know how many digits are in degrees; all but the last two.
1429 | // Switch from BCD (digits) to binary minutes.
1430 | decimal = 1;
1431 | #ifdef GPS_FIX_LOCATION
1432 | uint8_t *valBCD = (uint8_t *) &val;
1433 | #else
1434 | uint8_t *valBCD = (uint8_t *) &dms;
1435 | #endif
1436 | uint8_t deg = to_binary( valBCD[1] );
1437 | if (valBCD[2] != 0)
1438 | deg += 100; // only possible if abs(longitude) >= 100.0 degrees
1439 |
1440 | // Convert val to minutes
1441 | uint8_t min = to_binary( valBCD[0] );
1442 |
1443 | if (validateFields() && (min >= 60))
1444 | sentenceInvalid();
1445 | else {
1446 | #ifdef GPS_FIX_LOCATION
1447 | val = (deg * 60) + min;
1448 | #endif
1449 | #ifdef GPS_FIX_LOCATION_DMS
1450 | dms.degrees = deg;
1451 | dms.minutes = min;
1452 | scratchpad.U4 = 0;
1453 | #endif
1454 | }
1455 |
1456 | if (chr == '.') return done;
1457 | }
1458 |
1459 | if (chr == ',') {
1460 | // If the last chars in ".mmmmmm" were not received,
1461 | // force the value into its final state.
1462 |
1463 | #ifdef GPS_FIX_LOCATION_DMS
1464 | if (decimal <= 5) {
1465 | if (decimal == 5)
1466 | scratchpad.U4 *= 10;
1467 | else if (decimal == 4)
1468 | scratchpad.U4 *= 100;
1469 | else if (decimal == 3)
1470 | scratchpad.U4 *= 1000;
1471 | else if (decimal == 2)
1472 | scratchpad.U4 *= 10000;
1473 |
1474 | finalizeDMS( scratchpad.U4, dms );
1475 | }
1476 | #endif
1477 |
1478 | #ifdef GPS_FIX_LOCATION
1479 | if (decimal == 4)
1480 | val *= 100;
1481 | else if (decimal == 5)
1482 | val *= 10;
1483 | else if (decimal == 6)
1484 | ;
1485 | else if (decimal > 6)
1486 | return true; // already converted at decimal==7
1487 | else if (decimal == 3)
1488 | val *= 1000;
1489 | else if (decimal == 2)
1490 | val *= 10000;
1491 | else if (decimal == 1)
1492 | val *= 100000;
1493 |
1494 | // Convert minutes x 1000000 to degrees x 10000000.
1495 | val += divu3(val*2 + 1); // same as 10 * ((val+30)/60) without trunc
1496 | #endif
1497 |
1498 | done = true;
1499 |
1500 | } else if (validateChars() && !isdigit(chr)) {
1501 | sentenceInvalid();
1502 |
1503 | } else if (!decimal) {
1504 | // BCD until *after* decimal point
1505 |
1506 | #ifdef GPS_FIX_LOCATION
1507 | val = (val<<4) | (chr - '0');
1508 | #else
1509 | uint32_t *val = (uint32_t *) &dms;
1510 | *val = (*val<<4) | (chr - '0');
1511 | #endif
1512 |
1513 | } else {
1514 |
1515 | decimal++;
1516 |
1517 | #ifdef GPS_FIX_LOCATION_DMS
1518 | if (decimal <= 6) {
1519 | scratchpad.U4 = scratchpad.U4 * 10 + (chr - '0');
1520 | if (decimal == 6)
1521 | finalizeDMS( scratchpad.U4, dms );
1522 | }
1523 | #endif
1524 |
1525 | #ifdef GPS_FIX_LOCATION
1526 | if (decimal <= 6) {
1527 |
1528 | val = val*10 + (chr - '0');
1529 |
1530 | } else if (decimal == 7) {
1531 |
1532 | // Convert now, while we still have the 6th decimal digit
1533 | val += divu3(val*2 + 1); // same as 10 * ((val+30)/60) without trunc
1534 | if (chr >= '9')
1535 | val += 2;
1536 | else if (chr >= '4')
1537 | val += 1;
1538 | }
1539 | #endif
1540 | }
1541 |
1542 | #endif
1543 |
1544 | return done;
1545 |
1546 | } // parseDDDMM
1547 |
1548 | //----------------------------------------------------------------
1549 |
1550 | bool NMEAGPS::parseLat( char chr )
1551 | {
1552 | #if defined( GPS_FIX_LOCATION ) | defined( GPS_FIX_LOCATION_DMS )
1553 | if (chrCount == 0) {
1554 | group_valid = (chr != ',');
1555 | if (group_valid)
1556 | NMEAGPS_INVALIDATE( location );
1557 | }
1558 |
1559 | if (group_valid) {
1560 |
1561 | if (parseDDDMM
1562 | (
1563 | #if defined( GPS_FIX_LOCATION )
1564 | m_fix.location._lat,
1565 | #endif
1566 | #if defined( GPS_FIX_LOCATION_DMS )
1567 | m_fix.latitudeDMS,
1568 | #endif
1569 | chr
1570 | )) {
1571 |
1572 | if (validateFields()) {
1573 |
1574 | #if defined( GPS_FIX_LOCATION )
1575 | if (m_fix.location._lat > 900000000L)
1576 | sentenceInvalid();
1577 | #endif
1578 | #if defined( GPS_FIX_LOCATION_DMS )
1579 | if ((m_fix.latitudeDMS.degrees > 90) ||
1580 | ((m_fix.latitudeDMS.degrees == 90) &&
1581 | ( (m_fix.latitudeDMS.minutes > 0) ||
1582 | (m_fix.latitudeDMS.seconds_whole > 0) ||
1583 | (m_fix.latitudeDMS.seconds_frac > 0) )))
1584 | sentenceInvalid();
1585 | #endif
1586 | }
1587 | }
1588 | }
1589 | #endif
1590 |
1591 | return true;
1592 |
1593 | } // parseLatitude
1594 |
1595 | //----------------------------------------------------------------
1596 |
1597 | bool NMEAGPS::parseNS( char chr )
1598 | {
1599 | #if defined( GPS_FIX_LOCATION ) | defined( GPS_FIX_LOCATION_DMS )
1600 | if (group_valid) {
1601 |
1602 | if (chrCount == 0) {
1603 |
1604 | // First char can only be 'N' or 'S'
1605 | if (chr == 'S') {
1606 | #ifdef GPS_FIX_LOCATION
1607 | m_fix.location._lat = -m_fix.location._lat;
1608 | #endif
1609 | #ifdef GPS_FIX_LOCATION_DMS
1610 | m_fix.latitudeDMS.hemisphere = SOUTH_H;
1611 | #endif
1612 | } else if ((validateChars() | validateFields()) && (chr != 'N')) {
1613 | sentenceInvalid();
1614 | }
1615 |
1616 | // Second char can only be ','
1617 | } else if ((validateChars() | validateFields()) &&
1618 | ((chrCount > 1) || (chr != ','))) {
1619 | sentenceInvalid();
1620 | }
1621 | }
1622 | #endif
1623 |
1624 | return true;
1625 |
1626 | } // parseNS
1627 |
1628 | //----------------------------------------------------------------
1629 |
1630 | bool NMEAGPS::parseLon( char chr )
1631 | {
1632 | #if defined( GPS_FIX_LOCATION ) | defined( GPS_FIX_LOCATION_DMS )
1633 | if ((chr == ',') && (chrCount == 0))
1634 | group_valid = false;
1635 |
1636 | if (group_valid) {
1637 |
1638 | if (parseDDDMM
1639 | (
1640 | #if defined( GPS_FIX_LOCATION )
1641 | m_fix.location._lon,
1642 | #endif
1643 | #if defined( GPS_FIX_LOCATION_DMS )
1644 | m_fix.longitudeDMS,
1645 | #endif
1646 | chr
1647 | )) {
1648 |
1649 | if (validateFields()) {
1650 |
1651 | #if defined( GPS_FIX_LOCATION )
1652 | if (m_fix.location._lon > 1800000000L)
1653 | sentenceInvalid();
1654 | #endif
1655 | #if defined( GPS_FIX_LOCATION_DMS )
1656 | if ((m_fix.longitudeDMS.degrees > 180) ||
1657 | ((m_fix.longitudeDMS.degrees == 180) &&
1658 | ( (m_fix.longitudeDMS.minutes > 0) ||
1659 | (m_fix.longitudeDMS.seconds_whole > 0) ||
1660 | (m_fix.longitudeDMS.seconds_frac > 0) )))
1661 | sentenceInvalid();
1662 | #endif
1663 | }
1664 | }
1665 | }
1666 | #endif
1667 |
1668 | return true;
1669 |
1670 | } // parseLon
1671 |
1672 | //----------------------------------------------------------------
1673 |
1674 | bool NMEAGPS::parseEW( char chr )
1675 | {
1676 | #if defined( GPS_FIX_LOCATION ) | defined( GPS_FIX_LOCATION_DMS )
1677 | if (group_valid) {
1678 |
1679 | if (chrCount == 0) {
1680 | m_fix.valid.location = true; // assumption
1681 |
1682 | // First char can only be 'W' or 'E'
1683 | if (chr == 'W') {
1684 | #ifdef GPS_FIX_LOCATION
1685 | m_fix.location._lon = -m_fix.location._lon;
1686 | #endif
1687 | #ifdef GPS_FIX_LOCATION_DMS
1688 | m_fix.longitudeDMS.hemisphere = WEST_H;
1689 | #endif
1690 | } else if ((validateChars() | validateFields()) && (chr != 'E')) {
1691 | sentenceInvalid();
1692 | }
1693 |
1694 | // Second char can only be ','
1695 | } else if ((validateChars() | validateFields()) &&
1696 | ((chrCount > 1) || (chr != ','))) {
1697 | sentenceInvalid();
1698 | }
1699 | }
1700 | #endif
1701 |
1702 | return true;
1703 |
1704 | } // parseEW
1705 |
1706 | //----------------------------------------------------------------
1707 |
1708 | bool NMEAGPS::parseSpeed( char chr )
1709 | {
1710 | #ifdef GPS_FIX_SPEED
1711 | if (chrCount == 0)
1712 | NMEAGPS_INVALIDATE( speed );
1713 | if (parseFloat( m_fix.spd, chr, 3 )) {
1714 |
1715 | if (validateFields() && m_fix.valid.speed && negative)
1716 | sentenceInvalid();
1717 | else
1718 | m_fix.valid.speed = (chrCount != 0);
1719 | }
1720 | #endif
1721 |
1722 | return true;
1723 |
1724 | } // parseSpeed
1725 |
1726 | //----------------------------------------------------------------
1727 |
1728 | bool NMEAGPS::parseSpeedKph( char chr )
1729 | {
1730 | #ifdef GPS_FIX_SPEED
1731 | parseSpeed( chr );
1732 |
1733 | if ((chr == ',') && m_fix.valid.speed) {
1734 | uint32_t kph = m_fix.spd.int32_000();
1735 | // Convert to Nautical Miles/Hour
1736 | uint32_t nmiph = (kph * 1000) / gps_fix::M_PER_NMI;
1737 | m_fix.spd.whole = nmiph / 1000;
1738 | m_fix.spd.frac = (nmiph - m_fix.spd.whole*1000);
1739 | }
1740 | #endif
1741 |
1742 | return true;
1743 |
1744 | } // parseSpeedKph
1745 |
1746 | //----------------------------------------------------------------
1747 |
1748 | bool NMEAGPS::parseHeading( char chr )
1749 | {
1750 | #ifdef GPS_FIX_HEADING
1751 | if (chrCount == 0)
1752 | NMEAGPS_INVALIDATE( heading );
1753 | if (parseFloat( m_fix.hdg, chr, 2 )) {
1754 |
1755 | if (validateFields() && m_fix.valid.heading &&
1756 | (negative || (m_fix.hdg.whole >= 360)))
1757 | sentenceInvalid();
1758 | else
1759 | m_fix.valid.heading = (chrCount != 0);
1760 | }
1761 | #endif
1762 |
1763 | return true;
1764 |
1765 | } // parseHeading
1766 |
1767 | //----------------------------------------------------------------
1768 |
1769 | bool NMEAGPS::parseAlt(char chr )
1770 | {
1771 | #ifdef GPS_FIX_ALTITUDE
1772 | if (chrCount == 0)
1773 | NMEAGPS_INVALIDATE( altitude );
1774 | if (parseFloat( m_fix.alt, chr, 2 )) {
1775 | if (validateFields() && (m_fix.alt.whole < -1000))
1776 | sentenceInvalid();
1777 | else
1778 | m_fix.valid.altitude = (chrCount != 0);
1779 | }
1780 | #endif
1781 |
1782 | return true;
1783 |
1784 | } // parseAlt
1785 |
1786 | //----------------------------------------------------------------
1787 |
1788 | bool NMEAGPS::parseGeoidHeight( char chr )
1789 | {
1790 | #ifdef GPS_FIX_GEOID_HEIGHT
1791 | if (chrCount == 0)
1792 | NMEAGPS_INVALIDATE( geoidHeight );
1793 | if (parseFloat( m_fix.geoidHt, chr, 2 ))
1794 | m_fix.valid.geoidHeight = (chrCount != 0);
1795 | #endif
1796 |
1797 | return true;
1798 |
1799 | } // parseGeoidHeight
1800 |
1801 | //----------------------------------------------------------------
1802 |
1803 | bool NMEAGPS::parseSatellites( char chr )
1804 | {
1805 | #ifdef GPS_FIX_SATELLITES
1806 | if (chrCount == 0)
1807 | NMEAGPS_INVALIDATE( satellites );
1808 |
1809 | if (parseInt( m_fix.satellites, chr )) {
1810 | if (validateFields() && negative)
1811 | sentenceInvalid();
1812 | else
1813 | m_fix.valid.satellites = true;
1814 | }
1815 | #endif
1816 |
1817 | return true;
1818 |
1819 | } // parseSatellites
1820 |
1821 | //----------------------------------------------------------------
1822 |
1823 | bool NMEAGPS::parseHDOP( char chr )
1824 | {
1825 | #ifdef GPS_FIX_HDOP
1826 | if (chrCount == 0)
1827 | NMEAGPS_INVALIDATE( hdop );
1828 | if (parseFloat( m_fix.hdop, chr, 3 )) {
1829 | if (validateFields() && negative)
1830 | sentenceInvalid();
1831 | else
1832 | m_fix.valid.hdop = (chrCount != 0);
1833 | }
1834 | #endif
1835 |
1836 | return true;
1837 |
1838 | } // parseHDOP
1839 |
1840 | //----------------------------------------------------------------
1841 |
1842 | bool NMEAGPS::parseVDOP( char chr )
1843 | {
1844 | #ifdef GPS_FIX_VDOP
1845 | if (chrCount == 0)
1846 | NMEAGPS_INVALIDATE( vdop );
1847 | if (parseFloat( m_fix.vdop, chr, 3 )) {
1848 | if (validateFields() && negative)
1849 | sentenceInvalid();
1850 | else
1851 | m_fix.valid.vdop = (chrCount != 0);
1852 | }
1853 | #endif
1854 |
1855 | return true;
1856 |
1857 | } // parseVDOP
1858 |
1859 | //----------------------------------------------------------------
1860 |
1861 | bool NMEAGPS::parsePDOP( char chr )
1862 | {
1863 | #ifdef GPS_FIX_PDOP
1864 | if (chrCount == 0)
1865 | NMEAGPS_INVALIDATE( pdop );
1866 | if (parseFloat( m_fix.pdop, chr, 3 )) {
1867 | if (validateFields() && negative)
1868 | sentenceInvalid();
1869 | else
1870 | m_fix.valid.pdop = (chrCount != 0);
1871 | }
1872 | #endif
1873 |
1874 | return true;
1875 |
1876 | } // parsePDOP
1877 |
1878 | //----------------------------------------------------------------
1879 |
1880 | static const uint16_t MAX_ERROR_CM = 20000; // 200m is a large STD
1881 |
1882 | bool NMEAGPS::parse_lat_err( char chr )
1883 | {
1884 | #ifdef GPS_FIX_LAT_ERR
1885 | if (chrCount == 0)
1886 | NMEAGPS_INVALIDATE( lat_err );
1887 | if (parseFloat( m_fix.lat_err_cm, chr, 2 )) {
1888 | if (validateFields() &&
1889 | (negative || (m_fix.valid.lat_err > MAX_ERROR_CM)))
1890 | sentenceInvalid();
1891 | else
1892 | m_fix.valid.lat_err = (chrCount != 0);
1893 | }
1894 | #endif
1895 |
1896 | return true;
1897 |
1898 | } // parse_lat_err
1899 |
1900 | //----------------------------------------------------------------
1901 |
1902 | bool NMEAGPS::parse_lon_err( char chr )
1903 | {
1904 | #ifdef GPS_FIX_LON_ERR
1905 | if (chrCount == 0)
1906 | NMEAGPS_INVALIDATE( lon_err );
1907 | if (parseFloat( m_fix.lon_err_cm, chr, 2 )) {
1908 | if (validateFields() &&
1909 | (negative || (m_fix.valid.lon_err > MAX_ERROR_CM)))
1910 | sentenceInvalid();
1911 | else
1912 | m_fix.valid.lon_err = (chrCount != 0);
1913 | }
1914 | #endif
1915 |
1916 | return true;
1917 |
1918 | } // parse_lon_err
1919 |
1920 | //----------------------------------------------------------------
1921 |
1922 | bool NMEAGPS::parse_alt_err( char chr )
1923 | {
1924 | #ifdef GPS_FIX_ALT_ERR
1925 | if (chrCount == 0)
1926 | NMEAGPS_INVALIDATE( alt_err );
1927 | if (parseFloat( m_fix.alt_err_cm, chr, 2 )) {
1928 | if (validateFields() &&
1929 | (negative || (m_fix.valid.alt_err > MAX_ERROR_CM)))
1930 | sentenceInvalid();
1931 | else
1932 | m_fix.valid.alt_err = (chrCount != 0);
1933 | }
1934 | #endif
1935 |
1936 | return true;
1937 |
1938 | } // parse_alt_err
1939 |
1940 | //----------------------------------------------------------------
1941 |
1942 | const gps_fix NMEAGPS::read()
1943 | {
1944 | gps_fix fix;
1945 |
1946 | if (_fixesAvailable) {
1947 | lock();
1948 |
1949 | #if (NMEAGPS_FIX_MAX > 0)
1950 | _fixesAvailable--;
1951 | fix = buffer[ _firstFix ];
1952 | if (merging == EXPLICIT_MERGING)
1953 | // Prepare to accumulate all fixes in an interval
1954 | buffer[ _firstFix ].init();
1955 | if (++_firstFix >= NMEAGPS_FIX_MAX)
1956 | _firstFix = 0;
1957 | #else
1958 | if (is_safe()) {
1959 | _fixesAvailable = false;
1960 | fix = m_fix;
1961 | }
1962 | #endif
1963 |
1964 | unlock();
1965 | }
1966 |
1967 | return fix;
1968 |
1969 | } // read
1970 |
1971 | //----------------------------------------------------------------
1972 |
1973 | void NMEAGPS::poll( Stream *device, nmea_msg_t msg )
1974 | {
1975 | // Only the ublox documentation references talker ID "EI".
1976 | // Other manufacturer's devices use "II" and "GP" talker IDs for the GPQ sentence.
1977 | // However, "GP" is reserved for the GPS device, so it seems inconsistent
1978 | // to use that talker ID when requesting something from the GPS device.
1979 |
1980 | #if defined(NMEAGPS_PARSE_GGA) | defined(NMEAGPS_RECOGNIZE_ALL)
1981 | static const char gga[] __PROGMEM = "EIGPQ,GGA";
1982 | #endif
1983 | #if defined(NMEAGPS_PARSE_GLL) | defined(NMEAGPS_RECOGNIZE_ALL)
1984 | static const char gll[] __PROGMEM = "EIGPQ,GLL";
1985 | #endif
1986 | #if defined(NMEAGPS_PARSE_GSA) | defined(NMEAGPS_RECOGNIZE_ALL)
1987 | static const char gsa[] __PROGMEM = "EIGPQ,GSA";
1988 | #endif
1989 | #if defined(NMEAGPS_PARSE_GST) | defined(NMEAGPS_RECOGNIZE_ALL)
1990 | static const char gst[] __PROGMEM = "EIGPQ,GST";
1991 | #endif
1992 | #if defined(NMEAGPS_PARSE_GSV) | defined(NMEAGPS_RECOGNIZE_ALL)
1993 | static const char gsv[] __PROGMEM = "EIGPQ,GSV";
1994 | #endif
1995 | #if defined(NMEAGPS_PARSE_RMC) | defined(NMEAGPS_RECOGNIZE_ALL)
1996 | static const char rmc[] __PROGMEM = "EIGPQ,RMC";
1997 | #endif
1998 | #if defined(NMEAGPS_PARSE_VTG) | defined(NMEAGPS_RECOGNIZE_ALL)
1999 | static const char vtg[] __PROGMEM = "EIGPQ,VTG";
2000 | #endif
2001 | #if defined(NMEAGPS_PARSE_ZDA) | defined(NMEAGPS_RECOGNIZE_ALL)
2002 | static const char zda[] __PROGMEM = "EIGPQ,ZDA";
2003 | #endif
2004 |
2005 | static const char * const poll_msgs[] __PROGMEM =
2006 | {
2007 | #if defined(NMEAGPS_PARSE_GGA) | defined(NMEAGPS_RECOGNIZE_ALL)
2008 | gga,
2009 | #endif
2010 | #if defined(NMEAGPS_PARSE_GLL) | defined(NMEAGPS_RECOGNIZE_ALL)
2011 | gll,
2012 | #endif
2013 | #if defined(NMEAGPS_PARSE_GSA) | defined(NMEAGPS_RECOGNIZE_ALL)
2014 | gsa,
2015 | #endif
2016 | #if defined(NMEAGPS_PARSE_GST) | defined(NMEAGPS_RECOGNIZE_ALL)
2017 | gst,
2018 | #endif
2019 | #if defined(NMEAGPS_PARSE_GSV) | defined(NMEAGPS_RECOGNIZE_ALL)
2020 | gsv,
2021 | #endif
2022 | #if defined(NMEAGPS_PARSE_RMC) | defined(NMEAGPS_RECOGNIZE_ALL)
2023 | rmc,
2024 | #endif
2025 | #if defined(NMEAGPS_PARSE_VTG) | defined(NMEAGPS_RECOGNIZE_ALL)
2026 | vtg,
2027 | #endif
2028 | #if defined(NMEAGPS_PARSE_ZDA) | defined(NMEAGPS_RECOGNIZE_ALL)
2029 | zda
2030 | #endif
2031 | };
2032 |
2033 | if ((NMEA_FIRST_MSG <= msg) && (msg <= NMEA_LAST_MSG)) {
2034 | const __FlashStringHelper * pollCmd =
2035 | (const __FlashStringHelper *) pgm_read_ptr( &poll_msgs[msg-NMEA_FIRST_MSG] );
2036 | send_P( device, pollCmd );
2037 | }
2038 |
2039 | } // poll
2040 |
2041 | //----------------------------------------------------------------
2042 |
2043 | static void send_trailer( Stream *device, uint8_t crc )
2044 | {
2045 | device->print('*');
2046 |
2047 | char hexDigit = formatHex( crc>>4 );
2048 | device->print( hexDigit );
2049 |
2050 | hexDigit = formatHex( crc );
2051 | device->print( hexDigit );
2052 |
2053 | device->print( CR );
2054 | device->print( LF );
2055 |
2056 | } // send_trailer
2057 |
2058 | //----------------------------------------------------------------
2059 |
2060 | void NMEAGPS::send( Stream *device, const char *msg )
2061 | {
2062 | if (msg && *msg) {
2063 | if (*msg == '$')
2064 | msg++;
2065 | device->print('$');
2066 | uint8_t sent_trailer = 0;
2067 | uint8_t crc = 0;
2068 | while (*msg) {
2069 | if ((*msg == '*') || (sent_trailer > 0))
2070 | sent_trailer++;
2071 | else
2072 | crc ^= *msg;
2073 | device->print( *msg++ );
2074 | }
2075 |
2076 | if (!sent_trailer)
2077 | send_trailer( device, crc );
2078 | }
2079 |
2080 | } // send
2081 |
2082 | //----------------------------------------------------------------
2083 |
2084 | void NMEAGPS::send_P( Stream *device, const __FlashStringHelper *msg )
2085 | {
2086 | if (msg) {
2087 | const char *ptr = (const char *)msg;
2088 | char chr = pgm_read_byte(ptr++);
2089 |
2090 | device->print('$');
2091 | if (chr == '$')
2092 | chr = pgm_read_byte(ptr++);
2093 | uint8_t sent_trailer = 0;
2094 | uint8_t crc = 0;
2095 | while (chr) {
2096 | if ((chr == '*') || (sent_trailer > 0))
2097 | sent_trailer++;
2098 | else
2099 | crc ^= chr;
2100 | device->print( chr );
2101 |
2102 | chr = pgm_read_byte(ptr++);
2103 | }
2104 |
2105 | if (!sent_trailer)
2106 | send_trailer( device, crc );
2107 | }
2108 |
2109 | } // send_P
2110 |
--------------------------------------------------------------------------------
/NeoGPS/NMEAGPS.h:
--------------------------------------------------------------------------------
1 | #ifndef NMEAGPS_H
2 | #define NMEAGPS_H
3 |
4 | // Copyright (C) 2014-2017, SlashDevin
5 | //
6 | // This file is part of NeoGPS
7 | //
8 | // NeoGPS is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation, either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // NeoGPS is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with NeoGPS. If not, see .
20 |
21 | #include "CosaCompat.h"
22 |
23 | #include
24 | #ifdef __AVR__
25 | #include
26 | #endif
27 |
28 | #include "GPSfix.h"
29 | #include "NMEAGPS_cfg.h"
30 |
31 | //------------------------------------------------------
32 | //
33 | // NMEA 0183 Parser for generic GPS Modules.
34 | //
35 | // As bytes are received from the device, they affect the
36 | // internal FSM and set various members of the current /fix/.
37 | // As multiple sentences are received, they are (optionally)
38 | // merged into a single fix. When the last sentence in a time
39 | // interval (usually 1 second) is received, the fix is stored
40 | // in the (optional) buffer of fixes.
41 | //
42 | // Only these NMEA messages are parsed:
43 | // GGA, GLL, GSA, GST, GSV, RMC, VTG, and ZDA.
44 |
45 | class NMEAGPS
46 | {
47 | NMEAGPS & operator =( const NMEAGPS & );
48 | NMEAGPS( const NMEAGPS & );
49 |
50 | public:
51 |
52 | NMEAGPS();
53 |
54 | //.......................................................................
55 | // NMEA standard message types (aka "sentences")
56 |
57 | enum nmea_msg_t {
58 | NMEA_UNKNOWN,
59 |
60 | #if defined(NMEAGPS_PARSE_GGA) | defined(NMEAGPS_RECOGNIZE_ALL)
61 | NMEA_GGA,
62 | #endif
63 |
64 | #if defined(NMEAGPS_PARSE_GLL) | defined(NMEAGPS_RECOGNIZE_ALL)
65 | NMEA_GLL,
66 | #endif
67 |
68 | #if defined(NMEAGPS_PARSE_GSA) | defined(NMEAGPS_RECOGNIZE_ALL)
69 | NMEA_GSA,
70 | #endif
71 |
72 | #if defined(NMEAGPS_PARSE_GST) | defined(NMEAGPS_RECOGNIZE_ALL)
73 | NMEA_GST,
74 | #endif
75 |
76 | #if defined(NMEAGPS_PARSE_GSV) | defined(NMEAGPS_RECOGNIZE_ALL)
77 | NMEA_GSV,
78 | #endif
79 |
80 | #if defined(NMEAGPS_PARSE_RMC) | defined(NMEAGPS_RECOGNIZE_ALL)
81 | NMEA_RMC,
82 | #endif
83 |
84 | #if defined(NMEAGPS_PARSE_VTG) | defined(NMEAGPS_RECOGNIZE_ALL)
85 | NMEA_VTG,
86 | #endif
87 |
88 | #if defined(NMEAGPS_PARSE_ZDA) | defined(NMEAGPS_RECOGNIZE_ALL)
89 | NMEA_ZDA,
90 | #endif
91 |
92 | NMEAMSG_END // a bookend that tells how many enums there were
93 | };
94 |
95 | CONST_CLASS_DATA nmea_msg_t NMEA_FIRST_MSG = (nmea_msg_t) (NMEA_UNKNOWN+1);
96 | CONST_CLASS_DATA nmea_msg_t NMEA_LAST_MSG = (nmea_msg_t) (NMEAMSG_END-1);
97 |
98 | //=======================================================================
99 | // FIX-ORIENTED methods: available, read, overrun and handle
100 | //=======================================================================
101 | // The typical sketch should have something like this in loop():
102 | //
103 | // while (gps.available( Serial1 )) {
104 | // gps_fix fix = gps.read();
105 | // if (fix.valid.location) {
106 | // ...
107 | // }
108 | // }
109 |
110 | //.......................................................................
111 | // The available(...) functions return the number of *fixes* that
112 | // are available to be "read" from the fix buffer. The GPS port
113 | // object is passed in so a char can be read if port.available().
114 |
115 | uint8_t available( Stream & port )
116 | {
117 | if (processing_style == PS_POLLING)
118 | while (port.available())
119 | handle( port.read() );
120 | return _available();
121 | }
122 | uint8_t available() const volatile { return _available(); };
123 |
124 | //.......................................................................
125 | // Return the next available fix. When no more fixes
126 | // are available, it returns an empty fix.
127 |
128 | const gps_fix read();
129 |
130 | //.......................................................................
131 | // The OVERRUN flag is set whenever a fix is not read by the time
132 | // the next update interval starts. You must clear it when you
133 | // detect the condition.
134 |
135 | bool overrun() const { return _overrun; }
136 | void overrun( bool val ) { _overrun = val; }
137 |
138 | //.......................................................................
139 | // As characters are processed, they can be categorized as
140 | // INVALID (not part of this protocol), OK (accepted),
141 | // or COMPLETED (end-of-message).
142 |
143 | enum decode_t { DECODE_CHR_INVALID, DECODE_CHR_OK, DECODE_COMPLETED };
144 |
145 | //.......................................................................
146 | // Process one character, possibly saving a buffered fix.
147 | // It implements merging and coherency.
148 | // This can be called from an ISR.
149 |
150 | decode_t handle( uint8_t c );
151 |
152 | //=======================================================================
153 | // CHARACTER-ORIENTED methods: decode, fix and is_safe
154 | //=======================================================================
155 | //
156 | // *** MOST APPLICATIONS SHOULD USE THE FIX-ORIENTED METHODS ***
157 | //
158 | // Using `decode` is only necessary if you want finer control
159 | // on how fix information is filtered and merged.
160 | //
161 | // Process one character of an NMEA GPS sentence. The internal state
162 | // machine tracks what part of the sentence has been received. As the
163 | // sentence is received, members of the /fix/ structure are updated.
164 | // This character-oriented method *does not* buffer any fixes, and
165 | // /read()/ will always return an empty fix.
166 | //
167 | // @return DECODE_COMPLETED when a sentence has been completely received.
168 |
169 | NMEAGPS_VIRTUAL decode_t decode( char c );
170 |
171 | //.......................................................................
172 | // Current fix accessor.
173 | // *** MOST APPLICATIONS SHOULD USE read() TO GET THE CURRENT FIX ***
174 | // /fix/ will be constantly changing as characters are received.
175 | //
176 | // For example, fix().longitude() may return nonsense data if
177 | // characters for that field are currently being processed in /decode/.
178 |
179 | gps_fix & fix() { return m_fix; };
180 |
181 | // NOTE: /is_safe/ *must* be checked before accessing members of /fix/.
182 | // If you need access to the current /fix/ at any time, you should
183 | // use the FIX-ORIENTED methods.
184 |
185 | //.......................................................................
186 | // Determine whether the members of /fix/ are "currently" safe.
187 | // It will return true when a complete sentence and the CRC characters
188 | // have been received (or after a CR if no CRC is present).
189 | // It will return false after a new sentence starts.
190 |
191 | bool is_safe() const volatile { return (rxState == NMEA_IDLE); }
192 |
193 | // NOTE: When INTERRUPT_PROCESSING is enabled, is_safe()
194 | // and fix() could change at any time (i.e., they should be
195 | // considered /volatile/).
196 |
197 | //=======================================================================
198 | // DATA MEMBER accessors and mutators
199 | //=======================================================================
200 |
201 | //.......................................................................
202 | // Convert a nmea_msg_t to a PROGMEM string.
203 | // Useful for printing the sentence type instead of a number.
204 | // This can return "UNK" if the message is not a valid number.
205 |
206 | const __FlashStringHelper *string_for( nmea_msg_t msg ) const;
207 |
208 | //.......................................................................
209 | // Most recent NMEA sentence type received.
210 |
211 | nmea_msg_t nmeaMessage NEOGPS_BF(8);
212 |
213 | //.......................................................................
214 | // Storage for Talker and Manufacturer IDs
215 |
216 | #ifdef NMEAGPS_SAVE_TALKER_ID
217 | char talker_id[2];
218 | #endif
219 |
220 | #ifdef NMEAGPS_SAVE_MFR_ID
221 | char mfr_id[3];
222 | #endif
223 |
224 | //.......................................................................
225 | // Various parsing statistics
226 |
227 | #ifdef NMEAGPS_STATS
228 | struct statistics_t {
229 | uint32_t ok; // count of successfully parsed sentences
230 | uint32_t errors; // NMEA checksum or other message errors
231 | uint32_t chars;
232 | void init()
233 | {
234 | ok = 0L;
235 | errors = 0L;
236 | chars = 0L;
237 | }
238 | } statistics;
239 | #endif
240 |
241 | //.......................................................................
242 | // SATELLITE VIEW array
243 |
244 | #ifdef NMEAGPS_PARSE_SATELLITES
245 | struct satellite_view_t
246 | {
247 | uint8_t id;
248 | #ifdef NMEAGPS_PARSE_SATELLITE_INFO
249 | uint8_t elevation; // 0..99 deg
250 | uint16_t azimuth; // 0..359 deg
251 | uint8_t snr NEOGPS_BF(7); // 0..99 dBHz
252 | bool tracked NEOGPS_BF(1);
253 | #endif
254 | } NEOGPS_PACKED;
255 |
256 | satellite_view_t satellites[ NMEAGPS_MAX_SATELLITES ];
257 | uint8_t sat_count; // in the above array
258 |
259 | bool satellites_valid() const { return (sat_count >= m_fix.satellites); }
260 | #endif
261 |
262 | //.......................................................................
263 | // Reset the parsing process.
264 | // This is used internally after a CS error, or could be used
265 | // externally to abort processing if it has been too long
266 | // since any data was received.
267 |
268 | void reset()
269 | {
270 | rxState = NMEA_IDLE;
271 | }
272 |
273 | //=======================================================================
274 | // CORRELATING Arduino micros() WITH UTC.
275 | //=======================================================================
276 |
277 | #if defined(NMEAGPS_TIMESTAMP_FROM_PPS) | \
278 | defined(NMEAGPS_TIMESTAMP_FROM_INTERVAL)
279 | protected:
280 | uint32_t _UTCsecondStart;
281 | #if defined(NMEAGPS_TIMESTAMP_FROM_INTERVAL) & \
282 | ( defined(GPS_FIX_DATE) | defined(GPS_FIX_TIME) )
283 | uint32_t _IntervalStart; // quiet time just ended
284 | #endif
285 | public:
286 |
287 | // The micros() value when the current UTC second started
288 | uint32_t UTCsecondStart() const
289 | { lock();
290 | uint32_t ret = _UTCsecondStart;
291 | unlock();
292 | return ret;
293 | };
294 | void UTCsecondStart( uint32_t us ) { _UTCsecondStart = us; };
295 |
296 | // The elapsed time since the start of the current UTC second
297 | uint32_t UTCus() const { return micros() - UTCsecondStart(); };
298 | uint32_t UTCms() const { return UTCus() / 1000UL; };
299 |
300 | // If you have attached a Pin Change interrupt routine to the PPS pin:
301 | //
302 | // const int PPSpin = 5;
303 | // void PPSisr() { gps.UTCsecondStart( micros() ); };
304 | // void setup()
305 | // {
306 | // attachInterrupt( digitalPinToInterrupt(PPSpin), PPSisr, RISING );
307 | // }
308 | //
309 | // If you are using an Input Capture pin, calculate the elapsed
310 | // microseconds since the capture time (based on the TIMER
311 | // frequency):
312 | //
313 | // void savePPSus() // called as an ISR or from a test in loop
314 | // {
315 | // uint32_t elapsedUS = (currentCount - captureCount) * countUS;
316 | // gps.UTCsecondStart( micros() - elapsedUS );
317 | // }
318 | #endif
319 |
320 | //=======================================================================
321 | // COMMUNICATING WITH THE GPS DEVICE: poll, send and send_P
322 | //=======================================================================
323 |
324 | //.......................................................................
325 | // Request the specified NMEA sentence. Not all devices will respond.
326 |
327 | static void poll( Stream *device, nmea_msg_t msg );
328 |
329 | //.......................................................................
330 | // Send a message to the GPS device.
331 | // The '$' is optional, and the '*' and CS will be added automatically.
332 |
333 | static void send( Stream *device, const char *msg );
334 | static void send_P( Stream *device, const __FlashStringHelper *msg );
335 |
336 | #include "NMEAGPSprivate.h"
337 |
338 | } NEOGPS_PACKED;
339 |
340 | #endif
341 |
--------------------------------------------------------------------------------
/NeoGPS/NMEAGPS_cfg.h:
--------------------------------------------------------------------------------
1 | #ifndef NMEAGPS_CFG_H
2 | #define NMEAGPS_CFG_H
3 |
4 | // Copyright (C) 2014-2017, SlashDevin
5 | //
6 | // This file is part of NeoGPS
7 | //
8 | // NeoGPS is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation, either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // NeoGPS is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with NeoGPS. If not, see .
20 |
21 | #include "GPSfix_cfg.h"
22 |
23 | //------------------------------------------------------
24 | // Enable/disable the parsing of specific sentences.
25 | //
26 | // Configuring out a sentence prevents it from being recognized; it
27 | // will be completely ignored. (See also NMEAGPS_RECOGNIZE_ALL, below)
28 | //
29 | // FYI: Only RMC and ZDA contain date information. Other
30 | // sentences contain time information. Both date and time are
31 | // required if you will be doing time_t-to-clock_t operations.
32 |
33 | #define NMEAGPS_PARSE_GGA
34 | //#define NMEAGPS_PARSE_GLL
35 | //#define NMEAGPS_PARSE_GSA
36 | //#define NMEAGPS_PARSE_GSV
37 | //#define NMEAGPS_PARSE_GST
38 | #define NMEAGPS_PARSE_RMC
39 | //#define NMEAGPS_PARSE_VTG
40 | //#define NMEAGPS_PARSE_ZDA
41 |
42 | //------------------------------------------------------
43 | // Select which sentence is sent *last* by your GPS device
44 | // in each update interval. This can be used by your sketch
45 | // to determine when the GPS quiet time begins, and thus
46 | // when you can perform "some" time-consuming operations.
47 |
48 | #define LAST_SENTENCE_IN_INTERVAL NMEAGPS::NMEA_RMC
49 |
50 | // NOTE: For PUBX-only, PGRM and UBX configs, use
51 | // (NMEAGPS::nmea_msg_t)(NMEAGPS::NMEA_LAST_MSG+1)
52 | // Otherwise, use one of the standard NMEA messages:
53 | // NMEAGPS::NMEA_RMC
54 | //
55 | // ==> CONFIRM THIS WITH NMEAorder.INO <==
56 | //
57 | // If the NMEA_LAST_SENTENCE_IN_INTERVAL is not chosen
58 | // correctly, GPS data may be lost because the sketch
59 | // takes too long elsewhere when this sentence is received.
60 | // Also, fix members may contain information from different
61 | // time intervals (i.e., they are not coherent).
62 | //
63 | // If you don't know which sentence is the last one,
64 | // use NMEAorder.ino to list them. You do not have to select
65 | // the last sentence the device sends if you have disabled
66 | // it. Just select the last sentence that you have *enabled*.
67 |
68 | //------------------------------------------------------
69 | // Choose how multiple sentences are merged into a fix:
70 | // 1) No merging
71 | // Each sentence fills out its own fix; there could be
72 | // multiple sentences per interval.
73 | // 2) EXPLICIT_MERGING
74 | // All sentences in an interval are *safely* merged into one fix.
75 | // NMEAGPS_FIX_MAX must be >= 1.
76 | // An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
77 | // 3) IMPLICIT_MERGING
78 | // All sentences in an interval are merged into one fix, with
79 | // possible data loss. If a received sentence is rejected for
80 | // any reason (e.g., a checksum error), all the values are suspect.
81 | // The fix will be cleared; no members will be valid until new
82 | // sentences are received and accepted. This uses less RAM.
83 | // An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
84 | // Uncomment zero or one:
85 |
86 | #define NMEAGPS_EXPLICIT_MERGING
87 | //#define NMEAGPS_IMPLICIT_MERGING
88 |
89 | #ifdef NMEAGPS_IMPLICIT_MERGING
90 | #define NMEAGPS_MERGING NMEAGPS::IMPLICIT_MERGING
91 |
92 | // Nothing is done to the fix at the beginning of every sentence...
93 | #define NMEAGPS_INIT_FIX(m)
94 |
95 | // ...but we invalidate one part when it starts to get parsed. It *may* get
96 | // validated when the parsing is finished.
97 | #define NMEAGPS_INVALIDATE(m) m_fix.valid.m = false
98 |
99 | #else
100 |
101 | #ifdef NMEAGPS_EXPLICIT_MERGING
102 | #define NMEAGPS_MERGING NMEAGPS::EXPLICIT_MERGING
103 | #else
104 | #define NMEAGPS_MERGING NMEAGPS::NO_MERGING
105 | #define NMEAGPS_NO_MERGING
106 | #endif
107 |
108 | // When NOT accumulating (not IMPLICIT), invalidate the entire fix
109 | // at the beginning of every sentence...
110 | #define NMEAGPS_INIT_FIX(m) m.valid.init()
111 |
112 | // ...so the individual parts do not need to be invalidated as they are parsed
113 | #define NMEAGPS_INVALIDATE(m)
114 |
115 | #endif
116 |
117 | #if ( defined(NMEAGPS_NO_MERGING) + \
118 | defined(NMEAGPS_IMPLICIT_MERGING) + \
119 | defined(NMEAGPS_EXPLICIT_MERGING) ) > 1
120 | #error Only one MERGING technique should be enabled in NMEAGPS_cfg.h!
121 | #endif
122 |
123 | //------------------------------------------------------
124 | // Define the fix buffer size. The NMEAGPS object will hold on to
125 | // this many fixes before an overrun occurs. This can be zero,
126 | // but you have to be more careful about using gps.fix() structure,
127 | // because it will be modified as characters are received.
128 |
129 | #define NMEAGPS_FIX_MAX 1
130 |
131 | #if defined(NMEAGPS_EXPLICIT_MERGING) && (NMEAGPS_FIX_MAX == 0)
132 | #error You must define FIX_MAX >= 1 to allow EXPLICIT merging in NMEAGPS_cfg.h
133 | #endif
134 |
135 | //------------------------------------------------------
136 | // Define how fixes are dropped when the FIFO is full.
137 | // true = the oldest fix will be dropped, and the new fix will be saved.
138 | // false = the new fix will be dropped, and all old fixes will be saved.
139 |
140 | #define NMEAGPS_KEEP_NEWEST_FIXES true
141 |
142 | //------------------------------------------------------
143 | // Enable/Disable interrupt-style processing of GPS characters
144 | // If you are using one of the NeoXXSerial libraries,
145 | // to attachInterrupt, this must be defined.
146 | // Otherwise, it must be commented out.
147 |
148 | //#define NMEAGPS_INTERRUPT_PROCESSING
149 |
150 | #ifdef NMEAGPS_INTERRUPT_PROCESSING
151 | #define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_INTERRUPT
152 | #else
153 | #define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_POLLING
154 | #endif
155 |
156 | //------------------------------------------------------
157 | // Enable/disable the talker ID, manufacturer ID and proprietary message processing.
158 | //
159 | // First, some background information. There are two kinds of NMEA sentences:
160 | //
161 | // 1. Standard NMEA sentences begin with "$ttccc", where
162 | // "tt" is the talker ID, and
163 | // "ccc" is the variable-length sentence type (i.e., command).
164 | //
165 | // For example, "$GPGLL,..." is a GLL sentence (Geographic Lat/Long)
166 | // transmitted by talker "GP". This is the most common talker ID. Some
167 | // devices may report "$GNGLL,..." when a mix of GPS and non-GPS
168 | // satellites have been used to determine the GLL data.
169 | //
170 | // 2. Proprietary NMEA sentences (i.e., those unique to a particular
171 | // manufacturer) begin with "$Pmmmccc", where
172 | // "P" is the NMEA-defined prefix indicator for proprietary messages,
173 | // "mmm" is the 3-character manufacturer ID, and
174 | // "ccc" is the variable-length sentence type (it can be empty).
175 | //
176 | // No validation of manufacturer ID and talker ID is performed in this
177 | // base class. For example, although "GP" is a common talker ID, it is not
178 | // guaranteed to be transmitted by your particular device, and it IS NOT
179 | // REQUIRED. If you need validation of these IDs, or you need to use the
180 | // extra information provided by some devices, you have two independent
181 | // options:
182 | //
183 | // 1. Enable SAVING the ID: When /decode/ returns DECODE_COMPLETED, the
184 | // /talker_id/ and/or /mfr_id/ members will contain ID bytes. The entire
185 | // sentence will be parsed, perhaps modifying members of /fix/. You should
186 | // enable one or both IDs if you want the information in all sentences *and*
187 | // you also want to know the ID bytes. This adds two bytes of RAM for the
188 | // talker ID, and 3 bytes of RAM for the manufacturer ID.
189 | //
190 | // 2. Enable PARSING the ID: The virtual /parse_talker_id/ and
191 | // /parse_mfr_id/ will receive each ID character as it is parsed. If it
192 | // is not a valid ID, return /false/ to abort processing the rest of the
193 | // sentence. No CPU time will be wasted on the invalid sentence, and no
194 | // /fix/ members will be modified. You should enable this if you want to
195 | // ignore some IDs. You must override /parse_talker_id/ and/or
196 | // /parse_mfr_id/ in a derived class.
197 | //
198 |
199 | //#define NMEAGPS_SAVE_TALKER_ID
200 | //#define NMEAGPS_PARSE_TALKER_ID
201 |
202 | //#define NMEAGPS_PARSE_PROPRIETARY
203 | #ifdef NMEAGPS_PARSE_PROPRIETARY
204 | //#define NMEAGPS_SAVE_MFR_ID
205 | #define NMEAGPS_PARSE_MFR_ID
206 | #endif
207 |
208 | //------------------------------------------------------
209 | // Enable/disable tracking the current satellite array and,
210 | // optionally, all the info for each satellite.
211 | //
212 |
213 | //#define NMEAGPS_PARSE_SATELLITES
214 | //#define NMEAGPS_PARSE_SATELLITE_INFO
215 |
216 | #ifdef NMEAGPS_PARSE_SATELLITES
217 | #define NMEAGPS_MAX_SATELLITES (20)
218 |
219 | #ifndef GPS_FIX_SATELLITES
220 | #error GPS_FIX_SATELLITES must be defined in GPSfix.h!
221 | #endif
222 |
223 | #endif
224 |
225 | #if defined(NMEAGPS_PARSE_SATELLITE_INFO) & \
226 | !defined(NMEAGPS_PARSE_SATELLITES)
227 | #error NMEAGPS_PARSE_SATELLITES must be defined!
228 | #endif
229 |
230 | //------------------------------------------------------
231 | // Enable/disable gathering interface statistics:
232 | // CRC errors and number of sentences received
233 |
234 | #define NMEAGPS_STATS
235 |
236 | //------------------------------------------------------
237 | // Configuration item for allowing derived types of NMEAGPS.
238 | // If you derive classes from NMEAGPS, you *must* define NMEAGPS_DERIVED_TYPES.
239 | // If not defined, virtuals are not used, with a slight size (2 bytes) and
240 | // execution time savings.
241 |
242 | //#define NMEAGPS_DERIVED_TYPES
243 |
244 | #ifdef NMEAGPS_DERIVED_TYPES
245 | #define NMEAGPS_VIRTUAL virtual
246 | #else
247 | #define NMEAGPS_VIRTUAL
248 | #endif
249 |
250 | //-----------------------------------
251 | // See if DERIVED_TYPES is required
252 | #if (defined(NMEAGPS_PARSE_TALKER_ID) | defined(NMEAGPS_PARSE_MFR_ID)) & \
253 | !defined(NMEAGPS_DERIVED_TYPES)
254 | #error You must define NMEAGPS_DERIVED_TYPES in NMEAGPS.h in order to parse Talker and/or Mfr IDs!
255 | #endif
256 |
257 | //------------------------------------------------------
258 | // Becase the NMEA checksum is not very good at error detection, you can
259 | // choose to enable additional validity checks. This trades a little more
260 | // code and execution time for more reliability.
261 | //
262 | // Validation at the character level is a syntactic check only. For
263 | // example, integer fields must contain characters in the range 0..9,
264 | // latitude hemisphere letters can be 'N' or 'S'. Characters that are not
265 | // valid for a particular field will cause the entire sentence to be
266 | // rejected as an error, *regardless* of whether the checksum would pass.
267 | #define NMEAGPS_VALIDATE_CHARS false
268 |
269 | // Validation at the field level is a semantic check. For
270 | // example, latitude degrees must be in the range -90..+90.
271 | // Values that are not valid for a particular field will cause the
272 | // entire sentence to be rejected as an error, *regardless* of whether the
273 | // checksum would pass.
274 | #define NMEAGPS_VALIDATE_FIELDS false
275 |
276 | //------------------------------------------------------
277 | // Some devices may omit trailing commas at the end of some
278 | // sentences. This may prevent the last field from being
279 | // parsed correctly, because the parser for some types keep
280 | // the value in an intermediate state until the complete
281 | // field is received (e.g., parseDDDMM, parseFloat and
282 | // parseZDA).
283 | //
284 | // Enabling this will inject a simulated comma when the end
285 | // of a sentence is received and the last field parser
286 | // indicated that it still needs one.
287 |
288 | #define NMEAGPS_COMMA_NEEDED
289 |
290 | //------------------------------------------------------
291 | // Some applications may want to recognize a sentence type
292 | // without actually parsing any of the fields. Uncommenting
293 | // this define will allow the nmeaMessage member to be set
294 | // when *any* standard message is seen, even though that
295 | // message is not enabled by a NMEAGPS_PARSE_xxx define above.
296 | // No valid flags will be true for those sentences.
297 |
298 | #define NMEAGPS_RECOGNIZE_ALL
299 |
300 | //------------------------------------------------------
301 | // Sometimes, a little extra space is needed to parse an intermediate form.
302 | // This config items enables extra space.
303 |
304 | //#define NMEAGPS_PARSING_SCRATCHPAD
305 |
306 | //------------------------------------------------------
307 | // If you need to know the exact UTC time at *any* time,
308 | // not just after a fix arrives, you must calculate the
309 | // offset between the Arduino micros() clock and the UTC
310 | // time in a received fix. There are two ways to do this:
311 | //
312 | // 1) When the GPS quiet time ends and the new update interval begins.
313 | // The timestamp will be set when the first character (the '$') of
314 | // the new batch of sentences arrives from the GPS device. This is fairly
315 | // accurate, but it will be delayed from the PPS edge by the GPS device's
316 | // fix calculation time (usually ~100us). There is very little variance
317 | // in this calculation time (usually < 30us), so all timestamps are
318 | // delayed by a nearly-constant amount.
319 | //
320 | // NOTE: At update rates higher than 1Hz, the updates may arrive with
321 | // some increasing variance.
322 |
323 | //#define NMEAGPS_TIMESTAMP_FROM_INTERVAL
324 |
325 | // 2) From the PPS pin of the GPS module. It is up to the application
326 | // developer to decide how to capture that event. For example, you could:
327 | //
328 | // a) simply poll for it in loop and call UTCsecondStart(micros());
329 | // b) use attachInterrupt to call a Pin Change Interrupt ISR to save
330 | // the micros() at the time of the interrupt (see NMEAGPS.h), or
331 | // c) connect the PPS to an Input Capture pin. Set the
332 | // associated TIMER frequency, calculate the elapsed time
333 | // since the PPS edge, and add that to the current micros().
334 |
335 | //#define NMEAGPS_TIMESTAMP_FROM_PPS
336 |
337 | #if defined( NMEAGPS_TIMESTAMP_FROM_INTERVAL ) & \
338 | defined( NMEAGPS_TIMESTAMP_FROM_PPS )
339 | #error You cannot enable both TIMESTAMP_FROM_INTERVAL and PPS in NMEAGPS_cfg.h!
340 | #endif
341 |
342 | #endif
--------------------------------------------------------------------------------
/NeoGPS/NMEAGPSprivate.h:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2014-2017, SlashDevin
2 | //
3 | // This file is part of NeoGPS
4 | //
5 | // NeoGPS is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // NeoGPS is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with NeoGPS. If not, see .
17 |
18 | protected:
19 | //.......................................................................
20 | // Table entry for NMEA sentence type string and its offset
21 | // in enumerated nmea_msg_t. Proprietary sentences can be implemented
22 | // in derived classes by adding a second table. Additional tables
23 | // can be singly-linked through the /previous/ member. The instantiated
24 | // class's table is the head, and should be returned by the derived
25 | // /msg_table/ function. Tables should be sorted alphabetically.
26 |
27 | struct msg_table_t {
28 | uint8_t offset; // nmea_msg_t enum starting value
29 | const msg_table_t *previous;
30 | uint8_t size; // number of entries in table array
31 | const char * const *table; // array of NMEA sentence strings
32 | };
33 |
34 | static const msg_table_t nmea_msg_table __PROGMEM;
35 |
36 | NMEAGPS_VIRTUAL const msg_table_t *msg_table() const
37 | { return &nmea_msg_table; };
38 |
39 | //.......................................................................
40 | // These virtual methods can accept or reject
41 | // the talker ID (for standard sentences) or
42 | // the manufacturer ID (for proprietary sentences).
43 | // The default is to accept *all* IDs.
44 | // Override them if you want to reject certain IDs, or you want
45 | // to handle COMPLETED sentences from certain IDs differently.
46 |
47 | #ifdef NMEAGPS_PARSE_TALKER_ID
48 | NMEAGPS_VIRTUAL bool parseTalkerID( char ) { return true; };
49 | #endif
50 |
51 | #ifdef NMEAGPS_PARSE_PROPRIETARY
52 | #ifdef NMEAGPS_PARSE_MFR_ID
53 | NMEAGPS_VIRTUAL bool parseMfrID( char ) { return true; };
54 | #endif
55 | #endif
56 |
57 | public:
58 | //.......................................................................
59 | // Set all parsed data to initial values.
60 |
61 | void data_init()
62 | {
63 | fix().init();
64 |
65 | #ifdef NMEAGPS_PARSE_SATELLITES
66 | sat_count = 0;
67 | #endif
68 | }
69 |
70 | //.......................................................................
71 |
72 | enum merging_t { NO_MERGING, EXPLICIT_MERGING, IMPLICIT_MERGING };
73 | static const merging_t
74 | merging = NMEAGPS_MERGING; // see NMEAGPS_cfg.h
75 |
76 | enum processing_style_t { PS_POLLING, PS_INTERRUPT };
77 | static const processing_style_t
78 | processing_style = NMEAGPS_PROCESSING_STYLE; // see NMEAGPS_cfg.h
79 |
80 | static const bool keepNewestFixes = NMEAGPS_KEEP_NEWEST_FIXES;
81 |
82 | static const bool validateChars () { return NMEAGPS_VALIDATE_CHARS; }
83 | static const bool validateFields() { return NMEAGPS_VALIDATE_FIELDS; }
84 |
85 | //.......................................................................
86 | // Control access to this object. This preserves atomicity when
87 | // the processing style is interrupt-driven.
88 |
89 | void lock() const
90 | {
91 | if (processing_style == PS_INTERRUPT)
92 | noInterrupts();
93 | }
94 |
95 | void unlock() const
96 | {
97 | if (processing_style == PS_INTERRUPT)
98 | interrupts();
99 | }
100 |
101 | protected:
102 | //=======================================================================
103 | // PARSING FINITE-STATE MACHINE
104 | //=======================================================================
105 |
106 | // Current fix
107 | gps_fix m_fix;
108 |
109 | // Current parser state
110 | uint8_t crc; // accumulated CRC in the sentence
111 | uint8_t fieldIndex; // index of current field in the sentence
112 | uint8_t chrCount; // index of current character in current field
113 | uint8_t decimal; // digits received after the decimal point
114 | struct {
115 | bool negative NEOGPS_BF(1); // field had a leading '-'
116 | bool _comma_needed NEOGPS_BF(1); // field needs a comma to finish parsing
117 | bool group_valid NEOGPS_BF(1); // multi-field group valid
118 | bool _overrun NEOGPS_BF(1); // an entire fix was dropped
119 | bool _intervalComplete NEOGPS_BF(1); // automatically set after LAST received
120 | #if (NMEAGPS_FIX_MAX == 0)
121 | bool _fixesAvailable NEOGPS_BF(1);
122 | #endif
123 | #ifdef NMEAGPS_PARSE_PROPRIETARY
124 | bool proprietary NEOGPS_BF(1); // receiving proprietary message
125 | #endif
126 | } NEOGPS_PACKED;
127 |
128 | #ifdef NMEAGPS_PARSING_SCRATCHPAD
129 | union {
130 | uint32_t U4;
131 | uint16_t U2[2];
132 | uint8_t U1[4];
133 | } scratchpad;
134 | #endif
135 |
136 | bool comma_needed()
137 | {
138 | #ifdef NMEAGPS_COMMA_NEEDED
139 | return _comma_needed;
140 | #else
141 | return false;
142 | #endif
143 | }
144 |
145 | void comma_needed( bool value )
146 | {
147 | #ifdef NMEAGPS_COMMA_NEEDED
148 | _comma_needed = value;
149 | #endif
150 | }
151 |
152 | // Internal FSM states
153 | enum rxState_t {
154 | NMEA_IDLE, // Waiting for initial '$'
155 | NMEA_RECEIVING_HEADER, // Parsing sentence type field
156 | NMEA_RECEIVING_DATA, // Parsing fields up to the terminating '*'
157 | NMEA_RECEIVING_CRC // Receiving two-byte transmitted CRC
158 | };
159 | CONST_CLASS_DATA uint8_t NMEA_FIRST_STATE = NMEA_IDLE;
160 | CONST_CLASS_DATA uint8_t NMEA_LAST_STATE = NMEA_RECEIVING_CRC;
161 |
162 | rxState_t rxState NEOGPS_BF(8);
163 |
164 | //.......................................................................
165 |
166 | uint8_t _available() const volatile { return _fixesAvailable; };
167 |
168 | //.......................................................................
169 | // Buffered fixes.
170 |
171 | #if (NMEAGPS_FIX_MAX > 0)
172 | gps_fix buffer[ NMEAGPS_FIX_MAX ]; // could be empty, see NMEAGPS_cfg.h
173 | uint8_t _fixesAvailable;
174 | uint8_t _firstFix;
175 | uint8_t _currentFix;
176 | #endif
177 |
178 | //.......................................................................
179 | // Indicate that the next sentence should initialize the internal data.
180 | // This is useful for coherency or custom filtering.
181 |
182 | bool intervalComplete() const { return _intervalComplete; }
183 | void intervalComplete( bool val ) { _intervalComplete = val; }
184 |
185 | //.......................................................................
186 | // Identify when an update interval is completed, according to the
187 | // most recently-received sentence. In this base class, it just
188 | // looks at the nmeaMessage member. Derived classes may have
189 | // more complex, specific conditions.
190 |
191 | NMEAGPS_VIRTUAL bool intervalCompleted() const
192 | { return (nmeaMessage == LAST_SENTENCE_IN_INTERVAL); }
193 | // see NMEAGPS_cfg.h
194 |
195 | //.......................................................................
196 | // When a fix has been fully assembled from a batch of sentences, as
197 | // determined by the configured merging technique and ending with the
198 | // LAST_SENTENCE_IN_INTERVAL, it is stored in the (optional) buffer
199 | // of fixes. They are removed with /read()/.
200 |
201 | void storeFix();
202 |
203 | //=======================================================================
204 | // PARSING METHODS
205 | //=======================================================================
206 |
207 | //.......................................................................
208 | // Try to recognize an NMEA sentence type, after the IDs have been accepted.
209 |
210 | decode_t parseCommand( char c );
211 | decode_t parseCommand( const msg_table_t *msgs, uint8_t cmdCount, char c );
212 |
213 | //.......................................................................
214 | // Parse various NMEA sentences
215 |
216 | bool parseGGA( char chr );
217 | bool parseGLL( char chr );
218 | bool parseGSA( char chr );
219 | bool parseGST( char chr );
220 | bool parseGSV( char chr );
221 | bool parseRMC( char chr );
222 | bool parseVTG( char chr );
223 | bool parseZDA( char chr );
224 |
225 | //.......................................................................
226 | // Depending on the NMEA sentence type, parse one field of an expected type.
227 |
228 | NMEAGPS_VIRTUAL bool parseField( char chr );
229 |
230 | //.......................................................................
231 | // Parse the primary NMEA field types into /fix/ members.
232 |
233 | bool parseFix ( char chr ); // aka STATUS or MODE
234 | bool parseTime ( char chr );
235 | bool parseDDMMYY ( char chr );
236 | bool parseLat ( char chr );
237 | bool parseNS ( char chr );
238 | bool parseLon ( char chr );
239 | bool parseEW ( char chr );
240 | bool parseSpeed ( char chr );
241 | bool parseSpeedKph ( char chr );
242 | bool parseHeading ( char chr );
243 | bool parseAlt ( char chr );
244 | bool parseGeoidHeight( char chr );
245 | bool parseHDOP ( char chr );
246 | bool parseVDOP ( char chr );
247 | bool parsePDOP ( char chr );
248 | bool parse_lat_err ( char chr );
249 | bool parse_lon_err ( char chr );
250 | bool parse_alt_err ( char chr );
251 | bool parseSatellites ( char chr );
252 |
253 | // Helper macro for parsing the 4 consecutive fields of a location
254 | #define PARSE_LOC(i) case i: return parseLat( chr );\
255 | case i+1: return parseNS ( chr ); \
256 | case i+2: return parseLon( chr ); \
257 | case i+3: return parseEW ( chr );
258 |
259 | //.......................................................................
260 | // Parse floating-point numbers into a /whole_frac/
261 | // @return true when the value is fully populated.
262 |
263 | bool parseFloat( gps_fix::whole_frac & val, char chr, uint8_t max_decimal );
264 |
265 | //.......................................................................
266 | // Parse floating-point numbers into a uint16_t
267 | // @return true when the value is fully populated.
268 |
269 | bool parseFloat( uint16_t & val, char chr, uint8_t max_decimal );
270 |
271 | //.......................................................................
272 | // Parse NMEA lat/lon dddmm.mmmm degrees
273 |
274 | bool parseDDDMM
275 | (
276 | #if defined( GPS_FIX_LOCATION )
277 | int32_t & val,
278 | #endif
279 | #if defined( GPS_FIX_LOCATION_DMS )
280 | DMS_t & dms,
281 | #endif
282 | char chr
283 | );
284 |
285 | //.......................................................................
286 | // Parse integer into 8-bit int
287 | // @return true when non-empty value
288 |
289 | bool parseInt( uint8_t &val, uint8_t chr )
290 | {
291 | negative = false;
292 | bool is_comma = (chr == ',');
293 |
294 | if (chrCount == 0) {
295 | if (is_comma)
296 | return false; // empty field!
297 |
298 | if (((validateChars() || validateFields()) && (chr == '-')) ||
299 | (validateChars() && !isdigit( chr )))
300 | sentenceInvalid();
301 | else
302 | val = (chr - '0');
303 |
304 | } else if (!is_comma) {
305 |
306 | if (validateChars() && !isdigit( chr ))
307 | sentenceInvalid();
308 | else
309 | val = (val*10) + (chr - '0');
310 | }
311 | return true;
312 | }
313 |
314 | //.......................................................................
315 | // Parse integer into signed 8-bit int
316 | // @return true when non-empty value
317 |
318 | bool parseInt( int8_t &val, uint8_t chr )
319 | {
320 | bool is_comma = (chr == ',');
321 |
322 | if (chrCount == 0) {
323 | if (is_comma)
324 | return false; // empty field!
325 |
326 | negative = (chr == '-');
327 | if (negative) {
328 | comma_needed( true ); // to negate
329 | val = 0;
330 | } else if (validateChars() && !isdigit( chr )) {
331 | sentenceInvalid();
332 | } else {
333 | val = (chr - '0');
334 | }
335 | } else if (!is_comma) {
336 | val = (val*10) + (chr - '0');
337 |
338 | } else if (negative) {
339 | val = -val;
340 | }
341 |
342 | return true;
343 | }
344 |
345 | //.......................................................................
346 | // Parse integer into 16-bit int
347 | // @return true when non-empty value
348 |
349 | bool parseInt( uint16_t &val, uint8_t chr )
350 | {
351 | negative = false;
352 |
353 | bool is_comma = (chr == ',');
354 | if (chrCount == 0) {
355 | if (is_comma)
356 | return false; // empty field!
357 |
358 | if (((validateChars() || validateFields()) && (chr == '-')) ||
359 | (validateChars() && !isdigit( chr )))
360 | sentenceInvalid();
361 | else
362 | val = (chr - '0');
363 |
364 | } else if (!is_comma) {
365 |
366 | if (validateChars() && !isdigit( chr ))
367 | sentenceInvalid();
368 | else
369 | val = (val*10) + (chr - '0');
370 | }
371 | return true;
372 | }
373 |
374 | //.......................................................................
375 | // Parse integer into 32-bit int
376 | // @return true when non-empty value
377 |
378 | bool parseInt( uint32_t &val, uint8_t chr )
379 | {
380 | negative = false;
381 |
382 | bool is_comma = (chr == ',');
383 | if (chrCount == 0) {
384 | if (is_comma)
385 | return false; // empty field!
386 |
387 | if (((validateChars() || validateFields()) && (chr == '-')) ||
388 | (validateChars() && !isdigit( chr )))
389 | sentenceInvalid();
390 | else
391 | val = (chr - '0');
392 |
393 | } else if (!is_comma) {
394 |
395 | if (validateChars() && !isdigit( chr ))
396 | sentenceInvalid();
397 | else
398 | val = (val*10) + (chr - '0');
399 | }
400 | return true;
401 | }
402 |
403 | private:
404 | void sentenceBegin ();
405 | void sentenceOk ();
406 | void sentenceInvalid ();
407 | void sentenceUnrecognized();
408 | void headerReceived ();
409 |
410 |
--------------------------------------------------------------------------------
/NeoGPS/NeoGPS_cfg.h:
--------------------------------------------------------------------------------
1 | #ifndef NEOGPS_CFG
2 | #define NEOGPS_CFG
3 |
4 | // Copyright (C) 2014-2017, SlashDevin
5 | //
6 | // This file is part of NeoGPS
7 | //
8 | // NeoGPS is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation, either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // NeoGPS is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with NeoGPS. If not, see .
20 |
21 | //------------------------------------------------------------------------
22 | // Enable/disable packed data structures.
23 | //
24 | // Enabling packed data structures will use two less-portable language
25 | // features of GCC to reduce RAM requirements. Although it was expected to slightly increase execution time and code size, the reverse is true on 8-bit AVRs: the code is smaller and faster with packing enabled.
26 | //
27 | // Disabling packed data structures will be very portable to other
28 | // platforms. NeoGPS configurations will use slightly more RAM, and on
29 | // 8-bit AVRs, the speed is slightly slower, and the code is slightly
30 | // larger. There may be no choice but to disable packing on processors
31 | // that do not support packed structures.
32 | //
33 | // There may also be compiler-specific switches that affect packing and the
34 | // code which accesses packed members. YMMV.
35 |
36 | #include
37 |
38 | #ifdef __AVR__
39 | #define NEOGPS_PACKED_DATA
40 | #endif
41 |
42 | //------------------------------------------------------------------------
43 | // Based on the above define, choose which set of packing macros should
44 | // be used in the rest of the NeoGPS package. Do not change these defines.
45 |
46 | #ifdef NEOGPS_PACKED_DATA
47 |
48 | // This is for specifying the number of bits to be used for a
49 | // member of a struct. Booleans are typically one bit.
50 | #define NEOGPS_BF(b) :b
51 |
52 | // This is for requesting the compiler to pack the struct or class members
53 | // "as closely as possible". This is a compiler-dependent interpretation.
54 | #define NEOGPS_PACKED __attribute__((packed))
55 |
56 | #else
57 |
58 | // Let the compiler do whatever it wants.
59 |
60 | #define NEOGPS_PACKED
61 | #define NEOGPS_BF(b)
62 |
63 | #endif
64 |
65 | //------------------------------------------------------------------------
66 | // Accommodate C++ compiler and IDE changes.
67 | //
68 | // Declaring constants as class data instead of instance data helps avoid
69 | // collisions with #define names, and allows the compiler to perform more
70 | // checks on their usage.
71 | //
72 | // Until C++ 10 and IDE 1.6.8, initialized class data constants
73 | // were declared like this:
74 | //
75 | // static const = ;
76 | //
77 | // Now, non-simple types (e.g., float) must be declared as
78 | //
79 | // static constexpr = ;
80 | //
81 | // The good news is that this allows the compiler to optimize out an
82 | // expression that is "promised" to be "evaluatable" as a constant.
83 | // The bad news is that it introduces a new language keyword, and the old
84 | // code raises an error.
85 | //
86 | // TODO: Evaluate the requirement for the "static" keyword.
87 | // TODO: Evaluate using a C++ version preprocessor symbol for the #if.
88 | // #if __cplusplus >= 201103L (from XBee.h)
89 | //
90 | // The CONST_CLASS_DATA define will expand to the appropriate keywords.
91 | //
92 |
93 |
94 | #if ( \
95 | (ARDUINO < 10606) | \
96 | ((10700 <= ARDUINO) & (ARDUINO <= 10799 )) | \
97 | ((107000 <= ARDUINO) & (ARDUINO <= 107999)) \
98 | ) \
99 | & \
100 | !defined(ESP8266) // PlatformIO Pull Request #82
101 |
102 | #define CONST_CLASS_DATA static const
103 |
104 | #else
105 |
106 | #define CONST_CLASS_DATA static constexpr
107 |
108 | #endif
109 |
110 | //------------------------------------------------------------------------
111 | // The PROGMEM definitions are not correct for Zero, MKR1000 and
112 | // earlier versions of Teensy boards
113 |
114 | #if defined(ARDUINO_SAMD_MKRZERO) | \
115 | defined(ARDUINO_SAMD_ZERO) | \
116 | defined(ARDUINO_SAM_DUE) | \
117 | defined(ARDUINO_ARCH_ARC32) | \
118 | defined(__TC27XX__) | \
119 | (defined(TEENSYDUINO) && (TEENSYDUINO < 139))
120 | #undef pgm_read_ptr
121 | #define pgm_read_ptr(addr) (*(const void **)(addr))
122 | #endif
123 |
124 |
125 | #endif
126 |
--------------------------------------------------------------------------------
/NeoGPS/NeoTime.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2014-2017, SlashDevin
2 | //
3 | // This file is part of NeoGPS
4 | //
5 | // NeoGPS is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // NeoGPS is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with NeoGPS. If not, see .
17 |
18 | #include "NeoTime.h"
19 |
20 | // For strtoul declaration
21 | #include
22 |
23 | #include
24 |
25 | Print & operator<<( Print& outs, const NeoGPS::time_t& t )
26 | {
27 | outs.print( t.full_year( t.year ) );
28 | outs.write( '-' );
29 | if (t.month < 10) outs.write( '0' );
30 | outs.print( t.month );
31 | outs.write( '-' );
32 | if (t.date < 10) outs.write( '0' );
33 | outs.print( t.date );
34 | outs.write( ' ' );
35 | if (t.hours < 10) outs.write( '0' );
36 | outs.print( t.hours );
37 | outs.write( ':' );
38 | if (t.minutes < 10) outs.write( '0' );
39 | outs.print( t.minutes );
40 | outs.write( ':' );
41 | if (t.seconds < 10) outs.write( '0' );
42 | outs.print( t.seconds );
43 |
44 | return outs;
45 | }
46 |
47 | using NeoGPS::time_t;
48 |
49 | bool time_t::parse(str_P s)
50 | {
51 | static size_t BUF_MAX = 32;
52 | char buf[BUF_MAX];
53 | strcpy_P(buf, s);
54 | char* sp = &buf[0];
55 | uint16_t value = strtoul(sp, &sp, 10);
56 |
57 | if (*sp != '-') return false;
58 | year = value % 100;
59 | if (full_year() != value) return false;
60 |
61 | value = strtoul(sp + 1, &sp, 10);
62 | if (*sp != '-') return false;
63 | month = value;
64 |
65 | value = strtoul(sp + 1, &sp, 10);
66 | if (*sp != ' ') return false;
67 | date = value;
68 |
69 | value = strtoul(sp + 1, &sp, 10);
70 | if (*sp != ':') return false;
71 | hours = value;
72 |
73 | value = strtoul(sp + 1, &sp, 10);
74 | if (*sp != ':') return false;
75 | minutes = value;
76 |
77 | value = strtoul(sp + 1, &sp, 10);
78 | if (*sp != 0) return false;
79 | seconds = value;
80 |
81 | return (is_valid());
82 | }
83 |
84 | #ifdef TIME_EPOCH_MODIFIABLE
85 | uint16_t time_t::s_epoch_year = Y2K_EPOCH_YEAR;
86 | uint8_t time_t::s_epoch_offset = 0;
87 | uint8_t time_t::s_epoch_weekday = Y2K_EPOCH_WEEKDAY;
88 | uint8_t time_t::s_pivot_year = 0;
89 | #endif
90 |
91 | const uint8_t time_t::days_in[] __PROGMEM = {
92 | 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
93 | };
94 |
95 | time_t::time_t(clock_t c)
96 | {
97 | uint16_t dayno = c / SECONDS_PER_DAY;
98 | c -= dayno * (uint32_t) SECONDS_PER_DAY;
99 | day = weekday_for(dayno);
100 |
101 | uint16_t y = epoch_year();
102 | for (;;) {
103 | uint16_t days = days_per( y );
104 | if (dayno < days) break;
105 | dayno -= days;
106 | y++;
107 | }
108 | bool leap_year = is_leap(y);
109 | y -= epoch_year();
110 | y += epoch_offset();
111 | while (y > 100)
112 | y -= 100;
113 | year = y;
114 |
115 | month = 1;
116 | for (;;) {
117 | uint8_t days = pgm_read_byte(&days_in[month]);
118 | if (leap_year && (month == 2)) days++;
119 | if (dayno < days) break;
120 | dayno -= days;
121 | month++;
122 | }
123 | date = dayno + 1;
124 |
125 | hours = c / SECONDS_PER_HOUR;
126 |
127 | uint16_t c_ms;
128 | if (hours < 18) // save 16uS
129 | c_ms = (uint16_t) c - (hours * (uint16_t) SECONDS_PER_HOUR);
130 | else
131 | c_ms = c - (hours * (uint32_t) SECONDS_PER_HOUR);
132 | minutes = c_ms / SECONDS_PER_MINUTE;
133 | seconds = c_ms - (minutes * SECONDS_PER_MINUTE);
134 | }
135 |
136 | void time_t::init()
137 | {
138 | seconds =
139 | hours =
140 | minutes = 0;
141 | date = 1;
142 | month = 1;
143 | year = epoch_year() % 100;
144 | day = epoch_weekday();
145 | }
146 |
147 | time_t::operator clock_t() const
148 | {
149 | clock_t c = days() * SECONDS_PER_DAY;
150 | if (hours < 18)
151 | c += hours * (uint16_t) SECONDS_PER_HOUR;
152 | else
153 | c += hours * (uint32_t) SECONDS_PER_HOUR;
154 | c += minutes * (uint16_t) SECONDS_PER_MINUTE;
155 | c += seconds;
156 |
157 | return (c);
158 | }
159 |
160 | uint16_t time_t::days() const
161 | {
162 | uint16_t day_count = day_of_year();
163 |
164 | uint16_t y = full_year();
165 | while (y-- > epoch_year())
166 | day_count += days_per(y);
167 |
168 | return (day_count);
169 | }
170 |
171 | uint16_t time_t::day_of_year() const
172 | {
173 | uint16_t dayno = date - 1;
174 | bool leap_year = is_leap();
175 |
176 | for (uint8_t m = 1; m < month; m++) {
177 | dayno += pgm_read_byte(&days_in[m]);
178 | if (leap_year && (m == 2)) dayno++;
179 | }
180 |
181 | return (dayno);
182 | }
183 |
184 | #ifdef TIME_EPOCH_MODIFIABLE
185 | void time_t::use_fastest_epoch()
186 | {
187 | // Figure out when we were compiled and use the year for a really
188 | // fast epoch_year. Format "MMM DD YYYY"
189 | const char* compile_date = (const char *) PSTR(__DATE__);
190 | uint16_t compile_year = 0;
191 | for (uint8_t i = 7; i < 11; i++)
192 | compile_year = compile_year*10 + (pgm_read_byte(&compile_date[i]) - '0');
193 |
194 | // Temporarily set a Y2K epoch so we can figure out the day for
195 | // January 1 of this year
196 | epoch_year ( Y2K_EPOCH_YEAR );
197 | epoch_weekday ( Y2K_EPOCH_WEEKDAY );
198 |
199 | time_t this_year(0);
200 | this_year.year = compile_year % 100;
201 | this_year.set_day();
202 | uint8_t compile_weekday = this_year.day;
203 |
204 | epoch_year ( compile_year );
205 | epoch_weekday( compile_weekday );
206 | pivot_year ( this_year.year );
207 | }
208 | #endif
--------------------------------------------------------------------------------
/NeoGPS/NeoTime.h:
--------------------------------------------------------------------------------
1 | #ifndef TIME_H
2 | #define TIME_H
3 |
4 | // Copyright (C) 2014-2017, SlashDevin
5 | //
6 | // This file is part of NeoGPS
7 | //
8 | // NeoGPS is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation, either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // NeoGPS is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with NeoGPS. If not, see .
20 |
21 | #include "NeoGPS_cfg.h"
22 | #include "CosaCompat.h"
23 |
24 | namespace NeoGPS {
25 |
26 | //------------------------------------------------------
27 | // Enable/disable run-time modification of the epoch.
28 | // If this is defined, epoch mutators are available.
29 | // If this is not defined, the epoch is a hard-coded constant.
30 | // Only epoch accessors are available.
31 | //#define TIME_EPOCH_MODIFIABLE
32 |
33 | /**
34 | * Number of seconds elapsed since January 1 of the Epoch Year,
35 | * 00:00:00 +0000 (UTC).
36 | */
37 | typedef uint32_t clock_t;
38 |
39 | const uint8_t SECONDS_PER_MINUTE = 60;
40 | const uint8_t MINUTES_PER_HOUR = 60;
41 | const uint16_t SECONDS_PER_HOUR = (uint16_t) SECONDS_PER_MINUTE * MINUTES_PER_HOUR;
42 | const uint8_t HOURS_PER_DAY = 24;
43 | const uint32_t SECONDS_PER_DAY = (uint32_t) SECONDS_PER_HOUR * HOURS_PER_DAY;
44 | const uint8_t DAYS_PER_WEEK = 7;
45 |
46 | /**
47 | * Common date/time structure
48 | */
49 | struct time_t {
50 |
51 | enum weekday_t {
52 | SUNDAY = 1,
53 | MONDAY = 2,
54 | TUESDAY = 3,
55 | WEDNESDAY = 4,
56 | THURSDAY = 5,
57 | FRIDAY = 6,
58 | SATURDAY = 7
59 | };
60 |
61 | // NTP epoch year and weekday (Monday)
62 | static const uint16_t NTP_EPOCH_YEAR = 1900;
63 | static const uint8_t NTP_EPOCH_WEEKDAY = MONDAY;
64 |
65 | // POSIX epoch year and weekday (Thursday)
66 | static const uint16_t POSIX_EPOCH_YEAR = 1970;
67 | static const uint8_t POSIX_EPOCH_WEEKDAY = THURSDAY;
68 |
69 | // Y2K epoch year and weekday (Saturday)
70 | static const uint16_t Y2K_EPOCH_YEAR = 2000;
71 | static const uint8_t Y2K_EPOCH_WEEKDAY = SATURDAY;
72 |
73 | uint8_t seconds; //!< 00-59
74 | uint8_t minutes; //!< 00-59
75 | uint8_t hours; //!< 00-23
76 | uint8_t day; //!< 01-07 Day of Week
77 | uint8_t date; //!< 01-31 Day of Month
78 | uint8_t month; //!< 01-12
79 | uint8_t year; //!< 00-99
80 |
81 | /**
82 | * Constructor.
83 | */
84 | time_t() {}
85 |
86 | /**
87 | * Construct from seconds since the Epoch.
88 | * @param[in] c clock.
89 | */
90 | time_t(clock_t c);
91 |
92 | /**
93 | * Initialize to January 1 of the Epoch Year, 00:00:00
94 | */
95 | void init();
96 |
97 | /**
98 | * Convert to seconds.
99 | * @return seconds from epoch.
100 | */
101 | operator clock_t() const;
102 |
103 | /**
104 | * Offset by a number of seconds.
105 | * @param[in] offset in seconds.
106 | */
107 | void operator +=( clock_t offset )
108 | { *this = offset + operator clock_t(); }
109 |
110 | /**
111 | * Set day member from current value. This is a relatively expensive
112 | * operation, so the weekday is only calculated when requested.
113 | */
114 | void set_day()
115 | {
116 | day = weekday_for(days());
117 | }
118 |
119 | /**
120 | * Convert to days.
121 | * @return days from January 1 of the epoch year.
122 | */
123 | uint16_t days() const;
124 |
125 | /**
126 | * Calculate day of the current year.
127 | * @return days from January 1, which is day zero.
128 | */
129 | uint16_t day_of_year() const;
130 |
131 | /**
132 | * Calculate 4-digit year from internal 2-digit year member.
133 | * @return 4-digit year.
134 | */
135 | uint16_t full_year() const
136 | {
137 | return full_year(year);
138 | }
139 |
140 | /**
141 | * Calculate 4-digit year from a 2-digit year
142 | * @param[in] year (4-digit).
143 | * @return true if /year/ is a leap year.
144 | */
145 | static uint16_t full_year( uint8_t year )
146 | {
147 | uint16_t y = year;
148 |
149 | if (y < pivot_year())
150 | y += 100 * (epoch_year()/100 + 1);
151 | else
152 | y += 100 * (epoch_year()/100);
153 |
154 | return y;
155 | }
156 |
157 | /**
158 | * Determine whether the current year is a leap year.
159 | * @returns true if the two-digit /year/ member is a leap year.
160 | */
161 | bool is_leap() const
162 | {
163 | return is_leap(full_year());
164 | }
165 |
166 | /**
167 | * Determine whether the 4-digit /year/ is a leap year.
168 | * @param[in] year (4-digit).
169 | * @return true if /year/ is a leap year.
170 | */
171 | static bool is_leap(uint16_t year)
172 | {
173 | if (year % 4) return false;
174 | uint16_t y = year % 400;
175 | return (y == 0) || ((y != 100) && (y != 200) && (y != 300));
176 | }
177 |
178 | /**
179 | * Calculate how many days are in the specified year.
180 | * @param[in] year (4-digit).
181 | * @return number of days.
182 | */
183 | static uint16_t days_per(uint16_t year)
184 | {
185 | return (365 + is_leap(year));
186 | }
187 |
188 | /**
189 | * Determine the day of the week for the specified day number
190 | * @param[in] dayno number as counted from January 1 of the epoch year.
191 | * @return weekday number 1..7, as for the /day/ member.
192 | */
193 | static uint8_t weekday_for(uint16_t dayno)
194 | {
195 | return ((dayno+epoch_weekday()-1) % DAYS_PER_WEEK) + 1;
196 | }
197 |
198 | /**
199 | * Check that all members, EXCEPT FOR day, are set to a coherent date/time.
200 | * @return true if valid date/time.
201 | */
202 | bool is_valid() const
203 | {
204 | return
205 | ((year <= 99) &&
206 | (1 <= month) && (month <= 12) &&
207 | ((1 <= date) &&
208 | ((date <= pgm_read_byte(&days_in[month])) ||
209 | ((month == 2) && is_leap() && (date == 29)))) &&
210 | (hours <= 23) &&
211 | (minutes <= 59) &&
212 | (seconds <= 59));
213 | }
214 |
215 | /**
216 | * Set the epoch year for all time_t operations. Note that the pivot
217 | * year defaults to the epoch_year % 100. Valid years will be in the
218 | * range epoch_year..epoch_year+99. Selecting a different pivot year
219 | * will slide this range to the right.
220 | * @param[in] y epoch year to set.
221 | * See also /full_year/.
222 | */
223 | #ifdef TIME_EPOCH_MODIFIABLE
224 | static void epoch_year(uint16_t y)
225 | {
226 | s_epoch_year = y;
227 | epoch_offset( s_epoch_year % 100 );
228 | pivot_year( epoch_offset() );
229 | }
230 | #endif
231 |
232 | /**
233 | * Get the epoch year.
234 | * @return year.
235 | */
236 | static uint16_t epoch_year()
237 | {
238 | return (s_epoch_year);
239 | }
240 |
241 | static uint8_t epoch_weekday() { return s_epoch_weekday; };
242 | #ifdef TIME_EPOCH_MODIFIABLE
243 | static void epoch_weekday( uint8_t ew ) { s_epoch_weekday = ew; };
244 | #endif
245 |
246 | /**
247 | * The pivot year determine the range of years WRT the epoch_year
248 | * For example, an epoch year of 2000 and a pivot year of 80 will
249 | * allow years in the range 1980 to 2079. Default 0 for Y2K_EPOCH.
250 | */
251 | static uint8_t pivot_year() { return s_pivot_year; };
252 | #ifdef TIME_EPOCH_MODIFIABLE
253 | static void pivot_year( uint8_t py ) { s_pivot_year = py; };
254 | #endif
255 |
256 | #ifdef TIME_EPOCH_MODIFIABLE
257 | /**
258 | * Use the current year for the epoch year. This will result in the
259 | * best performance of conversions, but dates/times before January 1
260 | * of the epoch year cannot be represented.
261 | */
262 | static void use_fastest_epoch();
263 | #endif
264 |
265 | /**
266 | * Parse a character string and fill out members.
267 | * @param[in] s PROGMEM character string with format "YYYY-MM-DD HH:MM:SS".
268 | * @return success.
269 | */
270 | bool parse(str_P s);
271 |
272 | static const uint8_t days_in[] PROGMEM; // month index is 1..12, PROGMEM
273 |
274 | protected:
275 | static uint8_t epoch_offset() { return s_epoch_offset; };
276 |
277 | #ifdef TIME_EPOCH_MODIFIABLE
278 | static void epoch_offset( uint8_t eo ) { s_epoch_offset = eo; };
279 |
280 | static uint16_t s_epoch_year;
281 | static uint8_t s_pivot_year;
282 | static uint8_t s_epoch_offset;
283 | static uint8_t s_epoch_weekday;
284 | #else
285 | static const uint16_t s_epoch_year = Y2K_EPOCH_YEAR;
286 | static const uint8_t s_pivot_year = s_epoch_year % 100;
287 | static const uint8_t s_epoch_offset = s_pivot_year;
288 | static const uint8_t s_epoch_weekday = Y2K_EPOCH_WEEKDAY;
289 | #endif
290 |
291 | } NEOGPS_PACKED;
292 |
293 | }; // namespace NeoGPS
294 |
295 | class Print;
296 |
297 | /**
298 | * Print the date/time to the given stream with the format "YYYY-MM-DD HH:MM:SS".
299 | * @param[in] outs output stream.
300 | * @param[in] t time structure.
301 | * @return iostream.
302 | */
303 | Print & operator <<( Print & outs, const NeoGPS::time_t &t );
304 |
305 | #endif
306 |
--------------------------------------------------------------------------------
/NeoGPS/README.md:
--------------------------------------------------------------------------------
1 | Modified NeoGPS library - minimal NMEA parsing.
2 |
--------------------------------------------------------------------------------
/NeoGPS/Streamers.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2014-2017, SlashDevin
2 | //
3 | // This file is part of NeoGPS
4 | //
5 | // NeoGPS is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // NeoGPS is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with NeoGPS. If not, see .
17 |
18 | #include "Streamers.h"
19 | #include "NMEAGPS.h"
20 |
21 | //#define USE_FLOAT
22 |
23 | Print& operator <<( Print &outs, const bool b )
24 | { outs.print( b ? 't' : 'f' ); return outs; }
25 |
26 | Print& operator <<( Print &outs, const char c ) { outs.print(c); return outs; }
27 |
28 | Print& operator <<( Print &outs, const uint16_t v ) { outs.print(v); return outs; }
29 |
30 | Print& operator <<( Print &outs, const uint32_t v ) { outs.print(v); return outs; }
31 |
32 | Print& operator <<( Print &outs, const int32_t v ) { outs.print(v); return outs; }
33 |
34 | Print& operator <<( Print &outs, const uint8_t v ) { outs.print(v); return outs; }
35 |
36 | Print& operator <<( Print &outs, const __FlashStringHelper *s )
37 | { outs.print(s); return outs; }
38 |
39 | //------------------------------------------
40 |
41 | const char gps_fix_header[] __PROGMEM =
42 | "Status,"
43 |
44 | #if defined(GPS_FIX_DATE) | defined(GPS_FIX_TIME)
45 |
46 | "UTC "
47 |
48 | #if defined(GPS_FIX_DATE)
49 | "Date"
50 | #endif
51 | #if defined(GPS_FIX_DATE) & defined(GPS_FIX_TIME)
52 | "/"
53 | #endif
54 | #if defined(GPS_FIX_TIME)
55 | "Time"
56 | #endif
57 |
58 | #else
59 | "s"
60 | #endif
61 |
62 | ","
63 |
64 | #ifdef GPS_FIX_LOCATION
65 | "Lat,Lon,"
66 | #endif
67 |
68 | #ifdef GPS_FIX_LOCATION_DMS
69 | "DMS,"
70 | #endif
71 |
72 | #if defined(GPS_FIX_HEADING)
73 | "Hdg,"
74 | #endif
75 |
76 | #if defined(GPS_FIX_SPEED)
77 | "Spd,"
78 | #endif
79 |
80 | #ifdef GPS_FIX_VELNED
81 | "Vel N,E,D,"
82 | #endif
83 |
84 | #if defined(GPS_FIX_ALTITUDE)
85 | "Alt,"
86 | #endif
87 |
88 | #if defined(GPS_FIX_HDOP)
89 | "HDOP,"
90 | #endif
91 |
92 | #if defined(GPS_FIX_VDOP)
93 | "VDOP,"
94 | #endif
95 |
96 | #if defined(GPS_FIX_PDOP)
97 | "PDOP,"
98 | #endif
99 |
100 | #if defined(GPS_FIX_LAT_ERR)
101 | "Lat err,"
102 | #endif
103 |
104 | #if defined(GPS_FIX_LON_ERR)
105 | "Lon err,"
106 | #endif
107 |
108 | #if defined(GPS_FIX_ALT_ERR)
109 | "Alt err,"
110 | #endif
111 |
112 | #if defined(GPS_FIX_SPD_ERR)
113 | "Spd err,"
114 | #endif
115 |
116 | #if defined(GPS_FIX_HDG_ERR)
117 | "Hdg err,"
118 | #endif
119 |
120 | #if defined(GPS_FIX_TIME_ERR)
121 | "Time err,"
122 | #endif
123 |
124 | #if defined(GPS_FIX_GEOID_HEIGHT)
125 | "Geoid Ht,"
126 | #endif
127 |
128 | #if defined(GPS_FIX_SATELLITES)
129 | "Sats,"
130 | #endif
131 |
132 | ;
133 |
134 | //...............
135 |
136 | #ifdef GPS_FIX_LOCATION_DMS
137 |
138 | static void printDMS( Print & outs, const DMS_t & dms )
139 | {
140 | if (dms.degrees < 10)
141 | outs.write( '0' );
142 | outs.print( dms.degrees );
143 | outs.write( ' ' );
144 |
145 | if (dms.minutes < 10)
146 | outs.write( '0' );
147 | outs.print( dms.minutes );
148 | outs.print( F("\' ") );
149 |
150 | if (dms.seconds_whole < 10)
151 | outs.write( '0' );
152 | outs.print( dms.seconds_whole );
153 | outs.write( '.' );
154 |
155 | if (dms.seconds_frac < 100)
156 | outs.write( '0' );
157 | if (dms.seconds_frac < 10)
158 | outs.write( '0' );
159 | outs.print( dms.seconds_frac );
160 | outs.print( F("\" ") );
161 |
162 | } // printDMS
163 |
164 | #endif
165 | //...............
166 |
167 | Print & operator <<( Print &outs, const gps_fix &fix )
168 | {
169 | if (fix.valid.status)
170 | outs << (uint8_t) fix.status;
171 | outs << ',';
172 |
173 | #if defined(GPS_FIX_DATE) | defined(GPS_FIX_TIME)
174 | bool someTime = false;
175 |
176 | #if defined(GPS_FIX_DATE)
177 | someTime |= fix.valid.date;
178 | #endif
179 |
180 | #if defined(GPS_FIX_TIME)
181 | someTime |= fix.valid.time;
182 | #endif
183 |
184 | if (someTime) {
185 | outs << fix.dateTime << '.';
186 | uint16_t ms = fix.dateTime_ms();
187 | if (ms < 100)
188 | outs << '0';
189 | if (ms < 10)
190 | outs << '0';
191 | outs << ms;
192 | }
193 | outs << ',';
194 |
195 | #else
196 |
197 | // Date/Time not enabled, just output the interval number
198 | static uint32_t sequence = 0L;
199 | outs << sequence++ << ',';
200 |
201 | #endif
202 |
203 | #ifdef USE_FLOAT
204 | #ifdef GPS_FIX_LOCATION
205 | if (fix.valid.location) {
206 | outs.print( fix.latitude(), 6 );
207 | outs << ',';
208 | outs.print( fix.longitude(), 6 );
209 | } else
210 | outs << ',';
211 | outs << ',';
212 | #endif
213 | #ifdef GPS_FIX_LOCATION_DMS
214 | if (fix.valid.location) {
215 | printDMS( outs, fix.latitudeDMS );
216 | outs.print( fix.latitudeDMS.NS() );
217 | outs.write( ' ' );
218 | if (fix.longitudeDMS.degrees < 100)
219 | outs.write( '0' );
220 | printDMS( outs, fix.longitudeDMS );
221 | outs.print( fix.longitudeDMS.EW() );
222 | }
223 | outs << ',';
224 | #endif
225 | #ifdef GPS_FIX_HEADING
226 | if (fix.valid.heading)
227 | outs.print( fix.heading(), 2 );
228 | outs << ',';
229 | #endif
230 | #ifdef GPS_FIX_SPEED
231 | if (fix.valid.speed)
232 | outs.print( fix.speed(), 3 ); // knots
233 | outs << ',';
234 | #endif
235 | #ifdef GPS_FIX_VELNED
236 | if (fix.valid.velned)
237 | outs.print( fix.velocity_north ); // cm/s
238 | outs << ',';
239 | if (fix.valid.velned)
240 | outs.print( fix.velocity_east ); // cm/s
241 | outs << ',';
242 | if (fix.valid.velned)
243 | outs.print( fix.velocity_down ); // cm/s
244 | outs << ',';
245 | #endif
246 | #ifdef GPS_FIX_ALTITUDE
247 | if (fix.valid.altitude)
248 | outs.print( fix.altitude(), 2 );
249 | outs << ',';
250 | #endif
251 |
252 | #ifdef GPS_FIX_HDOP
253 | if (fix.valid.hdop)
254 | outs.print( (fix.hdop * 0.001), 3 );
255 | outs << ',';
256 | #endif
257 | #ifdef GPS_FIX_VDOP
258 | if (fix.valid.vdop)
259 | outs.print( (fix.vdop * 0.001), 3 );
260 | outs << ',';
261 | #endif
262 | #ifdef GPS_FIX_PDOP
263 | if (fix.valid.pdop)
264 | outs.print( (fix.pdop * 0.001), 3 );
265 | outs << ',';
266 | #endif
267 |
268 | #ifdef GPS_FIX_LAT_ERR
269 | if (fix.valid.lat_err)
270 | outs.print( fix.lat_err(), 2 );
271 | outs << ',';
272 | #endif
273 | #ifdef GPS_FIX_LON_ERR
274 | if (fix.valid.lon_err)
275 | outs.print( fix.lon_err(), 2 );
276 | outs << ',';
277 | #endif
278 | #ifdef GPS_FIX_ALT_ERR
279 | if (fix.valid.alt_err)
280 | outs.print( fix.alt_err(), 2 );
281 | outs << ',';
282 | #endif
283 | #ifdef GPS_FIX_SPD_ERR
284 | if (fix.valid.spd_err)
285 | outs.print( fix.spd_err(), 2 );
286 | outs << ',';
287 | #endif
288 | #ifdef GPS_FIX_HDG_ERR
289 | if (fix.valid.hdg_err)
290 | outs.print( fix.hdg_err(), 2 );
291 | outs << ',';
292 | #endif
293 | #ifdef GPS_FIX_TIME_ERR
294 | if (fix.valid.time_err)
295 | outs.print( fix.time_err(), 2 );
296 | outs << ',';
297 | #endif
298 |
299 | #ifdef GPS_FIX_GEOID_HEIGHT
300 | if (fix.valid.geoidHeight)
301 | outs.print( fix.geoidHeight(), 2 );
302 | outs << ',';
303 | #endif
304 |
305 | #else
306 |
307 | // not USE_FLOAT ----------------------
308 |
309 | #ifdef GPS_FIX_LOCATION
310 | if (fix.valid.location)
311 | outs << fix.latitudeL() << ',' << fix.longitudeL();
312 | else
313 | outs << ',';
314 | outs << ',';
315 | #endif
316 | #ifdef GPS_FIX_LOCATION_DMS
317 | if (fix.valid.location) {
318 | printDMS( outs, fix.latitudeDMS );
319 | outs.print( fix.latitudeDMS.NS() );
320 | outs.write( ' ' );
321 | if (fix.longitudeDMS.degrees < 100)
322 | outs.write( '0' );
323 | printDMS( outs, fix.longitudeDMS );
324 | outs.print( fix.longitudeDMS.EW() );
325 | }
326 | outs << ',';
327 | #endif
328 | #ifdef GPS_FIX_HEADING
329 | if (fix.valid.heading)
330 | outs << fix.heading_cd();
331 | outs << ',';
332 | #endif
333 | #ifdef GPS_FIX_SPEED
334 | if (fix.valid.speed)
335 | outs << fix.speed_mkn();
336 | outs << ',';
337 | #endif
338 | #ifdef GPS_FIX_VELNED
339 | if (fix.valid.velned)
340 | outs.print( fix.velocity_north ); // cm/s
341 | outs << ',';
342 | if (fix.valid.velned)
343 | outs.print( fix.velocity_east ); // cm/s
344 | outs << ',';
345 | if (fix.valid.velned)
346 | outs.print( fix.velocity_down ); // cm/s
347 | outs << ',';
348 | #endif
349 | #ifdef GPS_FIX_ALTITUDE
350 | if (fix.valid.altitude)
351 | outs << fix.altitude_cm();
352 | outs << ',';
353 | #endif
354 |
355 | #ifdef GPS_FIX_HDOP
356 | if (fix.valid.hdop)
357 | outs << fix.hdop;
358 | outs << ',';
359 | #endif
360 | #ifdef GPS_FIX_VDOP
361 | if (fix.valid.vdop)
362 | outs << fix.vdop;
363 | outs << ',';
364 | #endif
365 | #ifdef GPS_FIX_PDOP
366 | if (fix.valid.pdop)
367 | outs << fix.pdop;
368 | outs << ',';
369 | #endif
370 |
371 | #ifdef GPS_FIX_LAT_ERR
372 | if (fix.valid.lat_err)
373 | outs << fix.lat_err_cm;
374 | outs << ',';
375 | #endif
376 | #ifdef GPS_FIX_LON_ERR
377 | if (fix.valid.lon_err)
378 | outs << fix.lon_err_cm;
379 | outs << ',';
380 | #endif
381 | #ifdef GPS_FIX_ALT_ERR
382 | if (fix.valid.alt_err)
383 | outs << fix.alt_err_cm;
384 | outs << ',';
385 | #endif
386 | #ifdef GPS_FIX_SPD_ERR
387 | if (fix.valid.spd_err)
388 | outs.print( fix.spd_err_mmps );
389 | outs << ',';
390 | #endif
391 | #ifdef GPS_FIX_HDG_ERR
392 | if (fix.valid.hdg_err)
393 | outs.print( fix.hdg_errE5 );
394 | outs << ',';
395 | #endif
396 | #ifdef GPS_FIX_TIME_ERR
397 | if (fix.valid.time_err)
398 | outs.print( fix.time_err_ns );
399 | outs << ',';
400 | #endif
401 |
402 | #ifdef GPS_FIX_GEOID_HEIGHT
403 | if (fix.valid.geoidHeight)
404 | outs << fix.geoidHeight_cm();
405 | outs << ',';
406 | #endif
407 |
408 | #endif
409 |
410 | #ifdef GPS_FIX_SATELLITES
411 | if (fix.valid.satellites)
412 | outs << fix.satellites;
413 | outs << ',';
414 | #endif
415 |
416 | return outs;
417 | }
418 |
419 | //-----------------------------
420 |
421 | static const char NMEAGPS_header[] __PROGMEM =
422 | #if defined(NMEAGPS_TIMESTAMP_FROM_INTERVAL) | defined(NMEAGPS_TIMESTAMP_FROM_PPS)
423 | "micros(),"
424 | #endif
425 |
426 | #if defined(NMEAGPS_PARSE_SATELLITES)
427 | "[sat"
428 | #if defined(NMEAGPS_PARSE_SATELLITE_INFO)
429 | " elev/az @ SNR"
430 | #endif
431 | "],"
432 | #endif
433 |
434 | #ifdef NMEAGPS_STATS
435 | "Rx ok,Rx err,Rx chars,"
436 | #endif
437 |
438 | "";
439 |
440 | void trace_header( Print & outs )
441 | {
442 | outs.print( (const __FlashStringHelper *) &gps_fix_header[0] );
443 | outs.print( (const __FlashStringHelper *) &NMEAGPS_header[0] );
444 |
445 | outs << '\n';
446 | }
447 |
448 | //--------------------------
449 |
450 | void trace_all( Print & outs, const NMEAGPS &gps, const gps_fix &fix )
451 | {
452 | outs << fix;
453 |
454 | #if defined(NMEAGPS_TIMESTAMP_FROM_INTERVAL) | defined(NMEAGPS_TIMESTAMP_FROM_PPS)
455 | outs << gps.UTCsecondStart();
456 | outs << ',';
457 | #endif
458 |
459 | #if defined(NMEAGPS_PARSE_SATELLITES)
460 | outs << '[';
461 |
462 | for (uint8_t i=0; i < gps.sat_count; i++) {
463 | outs << gps.satellites[i].id;
464 |
465 | #if defined(NMEAGPS_PARSE_SATELLITE_INFO)
466 | outs << ' ' <<
467 | gps.satellites[i].elevation << '/' << gps.satellites[i].azimuth;
468 | outs << '@';
469 | if (gps.satellites[i].tracked)
470 | outs << gps.satellites[i].snr;
471 | else
472 | outs << '-';
473 | #endif
474 |
475 | outs << ',';
476 | }
477 |
478 | outs << F("],");
479 | #endif
480 |
481 | #ifdef NMEAGPS_STATS
482 | outs << gps.statistics.ok << ','
483 | << gps.statistics.errors << ','
484 | << gps.statistics.chars << ',';
485 | #endif
486 |
487 | outs << '\n';
488 |
489 | } // trace_all
490 |
--------------------------------------------------------------------------------
/NeoGPS/Streamers.h:
--------------------------------------------------------------------------------
1 | #ifndef STREAMERS_H
2 | #define STREAMERS_H
3 |
4 | // Copyright (C) 2014-2017, SlashDevin
5 | //
6 | // This file is part of NeoGPS
7 | //
8 | // NeoGPS is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation, either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // NeoGPS is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with NeoGPS. If not, see .
20 |
21 | #include
22 |
23 | extern Print & operator <<( Print & outs, const bool b );
24 | extern Print & operator <<( Print & outs, const char c );
25 | extern Print & operator <<( Print & outs, const uint16_t v );
26 | extern Print & operator <<( Print & outs, const uint32_t v );
27 | extern Print & operator <<( Print & outs, const int32_t v );
28 | extern Print & operator <<( Print & outs, const uint8_t v );
29 | extern Print & operator <<( Print & outs, const __FlashStringHelper *s );
30 |
31 | class gps_fix;
32 |
33 | /**
34 | * Print valid fix data to the given stream with the format
35 | * "status,dateTime,lat,lon,heading,speed,altitude,satellites,
36 | * hdop,vdop,pdop,lat_err,lon_err,alt_err"
37 | * The "header" above contains the actual compile-time configuration.
38 | * A comma-separated field will be empty if the data is NOT valid.
39 | * @param[in] outs output stream.
40 | * @param[in] fix gps_fix instance.
41 | * @return iostream.
42 | */
43 | extern Print & operator <<( Print &outs, const gps_fix &fix );
44 |
45 | class NMEAGPS;
46 |
47 | extern void trace_header( Print & outs );
48 | extern void trace_all( Print & outs, const NMEAGPS &gps, const gps_fix &fix );
49 |
50 | #endif
--------------------------------------------------------------------------------
/Program.h:
--------------------------------------------------------------------------------
1 | #ifndef PROGRAM_H
2 | #define PROGRAM_H
3 | enum Action: int {
4 | none = 0,
5 | displayIMUAttitude = 1,
6 | displayRealTimeClock = 2,
7 | displayGPS = 3,
8 | displayRangeWith1202Error = 4,
9 | setTime = 5,
10 | setDate = 6,
11 | PlayAudioclip = 7,
12 | PlaySelectedAudioclip = 8,
13 | displayIMUGyro = 9,
14 | lunarDecent = 10,
15 | countUpTimer = 11,
16 | idleMode = 12,
17 | pleasePreform = 13,
18 | apollo13Startup = 14
19 | };
20 |
21 | enum Mode: int {
22 | modeIdle = 0,
23 | modeInputVerb = 1,
24 | modeInputNoun = 2,
25 | modeInputProgram = 3,
26 | modeLampTest = 4
27 | };
28 |
29 | enum programNumber: int {
30 | programNone = 0,
31 | programJFKAudio = 62,
32 | programApollo11Audio = 69,
33 | programApollo13Audio = 70
34 | };
35 |
36 | enum lampNumber: int {
37 | lampNoun = 0,
38 | lampProg = 1,
39 | lampVerb = 2,
40 | lampCompActy = 3,
41 | lampTemp = 4,
42 | lampGimbalLock = 5,
43 | lampProgCond = 6,
44 | lampRestart = 7,
45 | lampTracker = 8,
46 | lampAlt = 9,
47 | lampVel = 10,
48 | lampClk = 11,
49 | lampPosition = 12,
50 | lampOprErr = 13,
51 | lampKeyRelease = 14,
52 | lampSTBY = 15,
53 | lampNoAtt = 16,
54 | lampUplinkActy = 17
55 |
56 | };
57 |
58 | enum color: int
59 | {
60 | green = 1,
61 | white = 2,
62 | yellow = 3,
63 | orange = 4,
64 | blue = 5,
65 | red = 6,
66 | off = 7
67 | };
68 |
69 | enum keyValues: int
70 | { // symbolic references to individual keys
71 | keyNone = 20,
72 | keyVerb = 10,
73 | keyNoun = 11,
74 | keyPlus = 12,
75 | keyMinus = 13,
76 | keyNumber0 = 0,
77 | keyNumber1 = 1,
78 | keyNumber2 = 2,
79 | keyNumber3 = 3,
80 | keyNumber4 = 4,
81 | keyNumber5 = 5,
82 | keyNumber6 = 6,
83 | keyNumber7 = 7,
84 | keyNumber8 = 8,
85 | keyNumber9 = 9,
86 | keyClear = 18,
87 | keyProceed = 14,
88 | keyRelease = 16,
89 | keyEnter = 15,
90 | keyReset = 17
91 | };
92 |
93 | enum verbValues: int
94 | { // Verbs 0,35,16,21
95 | verbNone = 0,
96 | verbLampTest = 35,
97 | verbExecuteMajorMode = 37,
98 | verbDisplayDecimal = 16,
99 | verbSetComponent = 21
100 | };
101 |
102 | enum nounValues: int
103 | { // Nouns 0,17,36,37,43,68,98
104 | nounNone = 0,
105 | nounIdleMode = 00,
106 | nounPleasePreform = 6,
107 | nounIMUAttitude = 17,
108 | nounIMUgyro = 18,
109 | nounApollo13StartUp = 20,
110 | nounCountUpTimer = 34,
111 | nounClockTime = 36,
112 | nounDate = 37,
113 | nounLatLongAltitude = 43,
114 | nounRangeTgoVelocity = 68,
115 | nounSelectAudioclip = 98
116 | };
117 |
118 | enum registerDisplayPositions: int
119 | { // Display Register Positions
120 | register1Position = 4,
121 | register2Position = 5,
122 | register3Position = 6
123 | };
124 | enum dregister: int
125 | {
126 | register1 = 1,
127 | register2 = 2,
128 | register3 = 3
129 | };
130 |
131 | enum imumode: int
132 | { // imumode Gyro or Accelration
133 | Gyro = 1,
134 | Accel = 0
135 | };
136 |
137 | enum inputnumsign: int
138 | { // imumode Gyro or Accelration
139 | plus = 1,
140 | minus = 0
141 | };
142 |
143 |
144 | byte verbnew[2];
145 | byte verbold[2];
146 | byte prognew[2];
147 | byte progold[2];
148 | byte nounnew[2];
149 | byte nounold[2];
150 | long valueForDisplay[7];
151 | byte digitValue[7][7];
152 | byte inputnum[5];
153 | int inputnumsign = plus;
154 | byte keyValue = keyNone;
155 | byte oldKey = none;
156 | bool fresh = true;
157 | byte action = none;
158 | byte currentAction = none;
159 | byte verb = verbNone;
160 | byte verb_old = verbNone;
161 | byte verb_old2 = verbNone;
162 | bool verb_error = false;
163 | byte verbNew[2];
164 | byte verbOld[2];
165 | byte noun = nounNone;
166 | bool noun_error = false;
167 | byte noun_old = nounNone;
168 | byte noun_old2 = nounNone;
169 | byte nounNew[2];
170 | byte nounOld[2];
171 | byte currentProgram = programNone;
172 | byte prog = 0;
173 | byte prog_old = 0;
174 | byte prog_old2 = 0;
175 | byte progNew[2];
176 | byte progOld[2];
177 | bool newProg = false;
178 | byte count = 0;
179 | byte mode = modeIdle;
180 | byte oldMode = modeIdle;
181 | bool toggle = false;
182 | bool stbyToggle = false;
183 | bool toggle600 = false;
184 | bool toggle250 = false;
185 | bool toggled250 = false;
186 | byte toggle600count = 0;
187 | byte toggleCount = 0;
188 | bool alarmStatus = false;
189 | bool toggle1201 = false;
190 | bool toggle1202 = false;
191 | bool error = 0;
192 | bool newAction = false;
193 | byte audioTrack = 1;
194 | bool blink = false;
195 | bool blinkverb = true;
196 | bool blinknoun = true;
197 | bool blinkprog = true;
198 | bool imutoggle = true;
199 | bool printregtoggle = true;
200 | bool uplink_compact_toggle = true;
201 | unsigned long blink_previousMillis = 0;
202 | const long blink_interval = 600;
203 | int pressedDuration = 0;
204 | int pressedDuration2 = 0;
205 | int clipnum = 1;
206 | int clipcount = 0;
207 | int fwdVelocity = 534;
208 | int verticalSpeed = -724;
209 | int radarAltitude = 3231;
210 | int lat = 0;
211 | int lon = 0;
212 | int alt = 0;
213 | uint32_t oneSecTimer = millis();
214 | uint32_t pressedTimer2 = millis();
215 |
216 | // IMU https://github.com/griegerc/arduino-gy521/blob/master/gy521-read-angle/gy521-read-angle.ino
217 | const int ACCEL_OFFSET = 200;
218 | const int GYRO_OFFSET = 151; // 151
219 | const int GYRO_SENSITITY = 131; // 131 is sensivity of gyro from data sheet
220 | const float GYRO_SCALE = 0.2; // 0.02 by default - tweak as required
221 | const float GYRO_TEMP_DRIFT = 0.02; // 0.02 by default - tweak as required
222 | const int GYRO_GRANGE = 2; // Gforce Range
223 | const int ACCEL_SCALE = 16384; // Scalefactor of Accelerometer
224 | const float LOOP_TIME = 0.15; // 0.1 = 100ms
225 | const int GYRO_OFFSET_X = 2; // change this to your system until gyroCorrX displays 0 if the DSKY sits still
226 | const int GYRO_OFFSET_Y = 0; // change this to your system until gyroCorrY displays 0 if the DSKY sits still
227 | const int GYRO_OFFSET_Z = 0; // change this to your system until gyroCorrZ displays 0 if the DSKY sits still
228 | const int ACC_OFFSET_X = 2; // change this to your system until accAngleX displays 0 if the DSKY sits still
229 | const int ACC_OFFSET_Y = 3; // change this to your system until accAngleY displays 0 if the DSKY sits still
230 | const int ACC_OFFSET_Z = 0; // change this to your system until accAngleZ displays 0 if the DSKY sits still
231 | int timerSeconds = 0;
232 | int timerMinutes = 0;
233 | int timerHours = 0;
234 | int globaltimer=0;
235 | bool global_state_1sec=false;
236 | bool global_state_600msec=false;
237 |
238 | // GPS Definitions
239 | bool GPS_READ_STARTED = true;
240 | bool gpsread = true;
241 | bool gpsfix = false;
242 |
243 | // variables for Time display (10th of a second, 100th of a second)
244 | unsigned long previousMillis = 0;
245 | int oldSecond = 0;
246 | uint32_t flashTimer = millis();
247 | uint32_t pressedTimer = millis();
248 |
249 | // 1sec toogle
250 | bool toggle_timer(void *)
251 | {
252 | if(global_state_1sec==false){
253 | global_state_1sec=true;
254 | toggle = true;
255 | }
256 | else
257 | {
258 | global_state_1sec=false;
259 | toggle = false;
260 | }
261 | return true; // repeat? true
262 | }
263 |
264 | // 600msec toggle
265 | bool toggle_timer_600(void *)
266 | {
267 | if(global_state_600msec==false){
268 | global_state_600msec=true;
269 | toggle600 = true;
270 | }
271 | else
272 | {
273 | global_state_600msec=false;
274 | toggle600 = false;
275 | }
276 | return true; // repeat? true
277 | }
278 |
279 | bool toggle_timer_250(void *) {
280 | toggle250 = !toggle250;
281 | return true; // repeat? true
282 | }
283 |
284 | int readKeyboard() {
285 | int oddRowDividerVoltage1 = 225;
286 | int oddRowDividerVoltage2 = 370;
287 | int oddRowDividerVoltage3 = 510;
288 | int oddRowDividerVoltage4 = 650;
289 | int oddRowDividerVoltage5 = 790;
290 | int oddRowDividerVoltage6 = 930;
291 |
292 | int evenRowDividerVoltage1 = 200;
293 | int evenRowDividerVoltage2 = 330;
294 | int evenRowDividerVoltage3 = 455;
295 | int evenRowDividerVoltage4 = 577;
296 | int evenRowDividerVoltage5 = 700;
297 | int evenRowDividerVoltage6 = 823;
298 | int evenRowDividerVoltage7 = 930;
299 |
300 | int value_row1 = analogRead(A0);
301 | int value_row2 = analogRead(A1);
302 | int value_row3 = analogRead(A2);
303 | if ((value_row1 > oddRowDividerVoltage6)
304 | && (value_row2 > oddRowDividerVoltage6)
305 | && (value_row3 > oddRowDividerVoltage6))
306 | {
307 | return keyNone; // no key
308 | }
309 |
310 | // keyboard ~top row
311 | else if (value_row1 < oddRowDividerVoltage1) return keyVerb;
312 | else if (value_row1 < oddRowDividerVoltage2) return keyPlus;
313 | else if (value_row1 < oddRowDividerVoltage3) return keyNumber7;
314 | else if (value_row1 < oddRowDividerVoltage4) return keyNumber8;
315 | else if (value_row1 < oddRowDividerVoltage5) return keyNumber9;
316 | else if (value_row1 < oddRowDividerVoltage6) return keyClear;
317 |
318 | // keyboard ~middle row
319 | else if (value_row2 < evenRowDividerVoltage1) return keyNoun;
320 | else if (value_row2 < evenRowDividerVoltage2) return keyMinus;
321 | else if (value_row2 < evenRowDividerVoltage3) return keyNumber4;
322 | else if (value_row2 < evenRowDividerVoltage4) return keyNumber5;
323 | else if (value_row2 < evenRowDividerVoltage5) return keyNumber6;
324 | else if (value_row2 < evenRowDividerVoltage6) return keyProceed;
325 | else if (value_row2 < evenRowDividerVoltage7) return keyEnter;
326 |
327 | // keyboard ~bottom row
328 | else if (value_row3 < oddRowDividerVoltage1) return keyNumber0;
329 | else if (value_row3 < oddRowDividerVoltage2) return keyNumber1;
330 | else if (value_row3 < oddRowDividerVoltage3) return keyNumber2;
331 | else if (value_row3 < oddRowDividerVoltage4) return keyNumber3;
332 | else if (value_row3 < oddRowDividerVoltage5) return keyRelease;
333 | else if (value_row3 < oddRowDividerVoltage6) return keyReset;
334 | else {
335 | // no key
336 | }
337 | }
338 | #endif
339 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OpenDSKY
2 |
3 | [](https://opendsky.backerkit.com)
4 |
5 | [](http://www.youtube.com/watch?v=I6qBGJz7ALo)
6 |
7 | ## Available Functions
8 |
9 | | Verb | Noun | Function |
10 | |:-------------:|:-------------:| -----|
11 | | 16 | 17 | Display IMU Gyro XYZ |
12 | | 16 | 18 | Display IMU Accel XYZ |
13 | | 16 | 19 | Display Date(Month/Day) & Time |
14 | | 16 | 33 | Countdown Timer/Alarm |
15 | | 16 | 36 | Read Time From RTC |
16 | | 16 | 43 | Display GPS Position & Altitude |
17 | | 16 | 46 | Display GPS Position & Heading |
18 | | 16 | 68 | Apollo 11 Lunar Descent/Landing Simulation |
19 | | 16 | 87 | Display IMU Values (All) |
20 | | 21 | 36 | Set the Time on RTC Module |
21 | | 35 | 00 | Lamp Test |
22 | | 37 | 00 | Idle Mode (P00) |
23 | | 37 | 06 | Apollo 13 Startup (Perform Task P06,V50,N25) [Hold PRO Key To Initiate Shutdown/Startup] |
24 |
25 |
26 |
27 |
28 | ## Required Libraries
29 | > DFPlayerMiniFast (Nano TX -> DFPlayer pin2): https://github.com/scottpav/DFPlayerMini_Fast
30 |
31 | > Adafruit NeoPixel Library: https://github.com/adafruit/Adafruit_NeoPixel
32 |
33 | > Led Control: https://github.com/wayoda/LedControl
34 |
35 | > RTCLib: https://github.com/adafruit/RTClib
36 |
--------------------------------------------------------------------------------
/Sound.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Sound.cpp
3 | *
4 | * Copyright 2018 Jeffrey Marten Gillmor
5 | *
6 | * OpenDSKY is a trademark of S&T GeoTronics LLC https://opendsky.com
7 | *
8 | * Driver to allow sounds to be played either the skip to next track button
9 | * can be used, or optionally with some wire mods to the openDSKY, the
10 | * softare serial can be used to TX commands.
11 | *
12 | * NASA have made some great sounds avialable here:
13 | * https://www.nasa.gov/connect/sounds/index.html
14 | *
15 | * Don't forget to update the TracksEnum in Sound.h to match the tracks you
16 | * have put onto the microSDcard.
17 | *
18 | * This program is free software: you can redistribute it and/or modify
19 | * it under the terms of the GNU General Public License as published by
20 | * the Free Software Foundation, either version 3 of the License, or
21 | * (at your option) any later version.
22 | *
23 | * This program is distributed in the hope that it will be useful,
24 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 | * (GNU General Public License for more details.
27 | *
28 | * You should have received a copy of the GNU Lesser General Public License
29 | * along with this program. If not, see .
30 | *
31 | */
32 | #include
33 | #include "Sound.h"
34 |
35 | DFPlayerMini_Fast myMP3;
36 |
37 | void soundSetup()
38 | {
39 | Serial.begin(9600);
40 |
41 | myMP3.begin(Serial);
42 |
43 |
44 |
45 | myMP3.sleep();
46 | delay(2000);
47 |
48 | myMP3.wakeUp();
49 | }
50 |
51 | void playTrack(uint16_t track)
52 | {
53 | myMP3.volume(30);
54 | delay(20);
55 | myMP3.play(track);
56 | delay(1000);
57 | }
58 |
--------------------------------------------------------------------------------
/Sound.h:
--------------------------------------------------------------------------------
1 | #ifndef SOUND_H
2 | #define SOUND_H
3 |
4 |
5 | typedef enum TRACKS_ENUM
6 | {
7 | HOUSTON,
8 | COUNTDOWN,
9 | LANDING,
10 | NUM_TRACKS
11 | }TracksEnum;
12 |
13 | extern void soundSetup(void);
14 | extern void playTrack(uint16_t track);
15 | #endif
16 |
--------------------------------------------------------------------------------
/audio_files .zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scottpav/OpenDSKY/5a0b6b7913226ab8e1ec231f57e34cbcc5232ebb/audio_files .zip
--------------------------------------------------------------------------------