├── 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 | [![IMAGE ALT TEXT HERE](https://fabcross.jp/news/2018/dmln53000006i503-img/dmln53000006i50t.jpg)](https://opendsky.backerkit.com) 4 | 5 | [![IMAGE ALT TEXT HERE](http://img.youtube.com/vi/I6qBGJz7ALo/0.jpg)](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 --------------------------------------------------------------------------------