diff options
Diffstat (limited to 'src/GPSService.cpp')
| -rw-r--r-- | src/GPSService.cpp | 509 |
1 files changed, 509 insertions, 0 deletions
diff --git a/src/GPSService.cpp b/src/GPSService.cpp new file mode 100644 index 0000000..4f90a0f --- /dev/null +++ b/src/GPSService.cpp @@ -0,0 +1,509 @@ +/* + * GPSService.cpp + * + * Created on: Aug 14, 2014 + * Author: Cameron Karlsson + */ + + +#include <nmeaparse/GPSService.h> +#include <nmeaparse/NumberConversion.h> + +#include <iostream> +#include <cmath> + +using namespace std; +using namespace std::chrono; + +using namespace nmea; + + +// ------ Some helpers ---------- +// Takes the NMEA lat/long format (dddmm.mmmm, [N/S,E/W]) and converts to degrees N,E only +double convertLatLongToDeg(string llstr, string dir){ + + double pd = parseDouble(llstr); + double deg = trunc(pd / 100); //get ddd from dddmm.mmmm + double mins = pd - deg * 100; + + deg = deg + mins / 60.0; + + char hdg = 'x'; + if (dir.size() > 0){ + hdg = dir[0]; + } + + //everything should be N/E, so flip S,W + if (hdg == 'S' || hdg == 'W'){ + deg *= -1.0; + } + + return deg; +} +double convertKnotsToKilometersPerHour(double knots){ + return knots * 1.852; +} + + + +// ------------- GPSSERVICE CLASS ------------- + + + + +GPSService::GPSService(NMEAParser& parser) { + attachToParser(parser); // attach to parser in the GPS object +} + +GPSService::~GPSService() { + // TODO Auto-generated destructor stub +} + +void GPSService::attachToParser(NMEAParser& _parser){ + + // http://www.gpsinformation.org/dale/nmea.htm + /* used sentences... + $GPGGA - time,position,fix data + $GPGSA - gps receiver operating mode, satellites used in position and DOP values + $GPGSV - number of gps satellites in view, satellite ID, elevation,azimuth, and SNR + $GPRMC - time,date, position,course, and speed data + $GPVTG - course and speed information relative to the ground + $GPZDA - 1pps timing message + $PSRF150 - gps module "ok to send" + */ + _parser.setSentenceHandler("PSRF150", [this](const NMEASentence& nmea){ + this->read_PSRF150(nmea); + }); + _parser.setSentenceHandler("GPGGA", [this](const NMEASentence& nmea){ + this->read_GPGGA(nmea); + }); + _parser.setSentenceHandler("GPGSA", [this](const NMEASentence& nmea){ + this->read_GPGSA(nmea); + }); + _parser.setSentenceHandler("GPGSV", [this](const NMEASentence& nmea){ + this->read_GPGSV(nmea); + }); + _parser.setSentenceHandler("GPRMC", [this](const NMEASentence& nmea){ + this->read_GPRMC(nmea); + }); + _parser.setSentenceHandler("GPVTG", [this](const NMEASentence& nmea){ + this->read_GPVTG(nmea); + }); + +} + + + + +void GPSService::read_PSRF150(const NMEASentence& nmea){ + // nothing right now... + // Called with checksum 3E (valid) for GPS turning ON + // Called with checksum 3F (invalid) for GPS turning OFF +} + +void GPSService::read_GPGGA(const NMEASentence& nmea){ + /* -- EXAMPLE -- + $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47 + + $GPGGA,205630.945,3346.1070,N,08423.6687,W,0,03,,30.8,M,-30.8,M,,0000*73 // ATLANTA!!!! + + Where: + GGA Global Positioning System Fix Data + index: + [0] 123519 Fix taken at 12:35:19 UTC + [1-2] 4807.038,N Latitude 48 deg 07.038' N + [3-4] 1131.000,E Longitude 11 deg 31.000' E + [5] 1 Fix quality: 0 = invalid + 1 = GPS fix (SPS) + 2 = DGPS fix + 3 = PPS fix + 4 = Real Time Kinematic + 5 = Float RTK + 6 = estimated (dead reckoning) (2.3 feature) + 7 = Manual input mode + 8 = Simulation mode + [6] 08 Number of satellites being tracked + [7] 0.9 Horizontal dilution of position + [8-9] 545.4,M Altitude, Meters, above mean sea level + [10-11] 46.9,M Height of geoid (mean sea level) above WGS84 + ellipsoid + [12] (empty field) time in seconds since last DGPS update + [13] (empty field) DGPS station ID number + [13] *47 the checksum data, always begins with * + */ + try + { + if (!nmea.checksumOK()){ + throw NMEAParseError("Checksum is invalid!"); + } + + if (nmea.parameters.size() < 14){ + throw NMEAParseError("GPS data is missing parameters."); + } + + + // TIMESTAMP + this->fix.timestamp.setTime(parseDouble(nmea.parameters[0])); + + string sll; + string dir; + // LAT + sll = nmea.parameters[1]; + dir = nmea.parameters[2]; + if (sll.size() > 0){ + this->fix.latitude = convertLatLongToDeg(sll, dir); + } + + // LONG + sll = nmea.parameters[3]; + dir = nmea.parameters[4]; + if (sll.size() > 0){ + this->fix.longitude = convertLatLongToDeg(sll, dir); + } + + + // FIX QUALITY + bool lockupdate = false; + this->fix.quality = (uint8_t)parseInt(nmea.parameters[5]); + if (this->fix.quality == 0){ + lockupdate = this->fix.setlock(false); + } + else if (this->fix.quality == 1){ + lockupdate = this->fix.setlock(true); + } + else {} + + + // TRACKING SATELLITES + this->fix.trackingSatellites = (int32_t)parseInt(nmea.parameters[6]); + if (this->fix.visibleSatellites < this->fix.trackingSatellites){ + this->fix.visibleSatellites = this->fix.trackingSatellites; // the visible count is in another sentence. + } + + // ALTITUDE + if (nmea.parameters[8].size() > 0){ + this->fix.altitude = parseDouble(nmea.parameters[8]); + } + else { + // leave old value + } + + //calling handlers + if (lockupdate){ + this->onLockStateChanged(this->fix.haslock); + } + this->onUpdate(); + } + catch (NumberConversionError& ex) + { + NMEAParseError pe("GPS Number Bad Format [$GPGGA] :: " + ex.message, nmea); + throw pe; + } + catch (NMEAParseError& ex) + { + NMEAParseError pe("GPS Data Bad Format [$GPGGA] :: " + ex.message, nmea); + throw pe; + } +} + +void GPSService::read_GPGSA(const NMEASentence& nmea){ + /* -- EXAMPLE -- + $GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39 + + $GPGSA,A,3,18,21,22,14,27,19,,,,,,,4.4,2.7,3.4*32 + + Where: + GSA Satellite status + [0] A Auto selection of 2D or 3D fix (M = manual) + [1] 3 3D fix - values include: 1 = no fix + 2 = 2D fix + 3 = 3D fix + [2-13] 04,05... PRNs of satellites used for fix (space for 12) + [14] 2.5 PDOP (dilution of precision) + [15] 1.3 Horizontal dilution of precision (HDOP) + [16] 2.1 Vertical dilution of precision (VDOP) + [16] *39 the checksum data, always begins with * + */ + + + try + { + if (!nmea.checksumOK()){ + throw NMEAParseError("Checksum is invalid!"); + } + + if (nmea.parameters.size() < 17){ + throw NMEAParseError("GPS data is missing parameters."); + } + + + // FIX TYPE + bool lockupdate = false; + uint64_t fixtype = parseInt(nmea.parameters[1]); + this->fix.type = (int8_t)fixtype; + if (fixtype == 1){ + lockupdate = this->fix.setlock(false); + } + else if (fixtype == 3) { + lockupdate = this->fix.setlock(true); + } + else {} + + + // DILUTION OF PRECISION -- PDOP + double dop = parseDouble(nmea.parameters[14]); + this->fix.dilution = dop; + + // HORIZONTAL DILUTION OF PRECISION -- HDOP + double hdop = parseDouble(nmea.parameters[15]); + this->fix.horizontalDilution = hdop; + + // VERTICAL DILUTION OF PRECISION -- VDOP + double vdop = parseDouble(nmea.parameters[16]); + this->fix.verticalDilution = vdop; + + //calling handlers + if (lockupdate){ + this->onLockStateChanged(this->fix.haslock); + } + this->onUpdate(); + } + catch (NumberConversionError& ex) + { + NMEAParseError pe("GPS Number Bad Format [$GPGSA] :: " + ex.message, nmea); + throw pe; + } + catch (NMEAParseError& ex) + { + NMEAParseError pe("GPS Data Bad Format [$GPGSA] :: " + ex.message, nmea); + throw pe; + } +} + +void GPSService::read_GPGSV(const NMEASentence& nmea){ + /* -- EXAMPLE -- + $GPGSV,2,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75 + + + $GPGSV,3,1,12,01,00,000,,02,00,000,,03,00,000,,04,00,000,*7C + $GPGSV,3,2,12,05,00,000,,06,00,000,,07,00,000,,08,00,000,*77 + $GPGSV,3,3,12,09,00,000,,10,00,000,,11,00,000,,12,00,000,*71 + + Where: + GSV Satellites in view + [0] 2 Number of sentences for full data + [1] 1 sentence 1 of 2 + [2] 08 Number of satellites in view + + [3] 01 Satellite PRN number + [4] 40 Elevation, degrees + [5] 083 Azimuth, degrees + [6] 46 SNR - higher is better + [...] for up to 4 satellites per sentence + [17] *75 the checksum data, always begins with * + */ + + try + { + if (!nmea.checksumOK()){ + throw NMEAParseError("Checksum is invalid!"); + } + + // can't do this check because the length varies depending on satallites... + //if(nmea.parameters.size() < 18){ + // throw NMEAParseError("GPS data is missing parameters."); + //} + + // VISIBLE SATELLITES + this->fix.visibleSatellites = (int32_t)parseInt(nmea.parameters[2]); + if (this->fix.trackingSatellites == 0){ + this->fix.visibleSatellites = 0; // if no satellites are tracking, then none are visible! + } // Also NMEA defaults to 12 visible when chip powers on. Obviously not right. + + uint32_t totalPages = (uint32_t)parseInt(nmea.parameters[0]); + uint32_t currentPage = (uint32_t)parseInt(nmea.parameters[1]); + + + //if this is the first page, then reset the almanac + if (currentPage == 1){ + this->fix.almanac.clear(); + //cout << "CLEARING ALMANAC" << endl; + } + + this->fix.almanac.lastPage = currentPage; + this->fix.almanac.totalPages = totalPages; + this->fix.almanac.visibleSize = this->fix.visibleSatellites; + + int entriesInPage = (nmea.parameters.size() - 3) >> 2; //first 3 are not satellite info + //- entries come in 4-ples, and truncate, so used shift + GPSSatellite sat; + for (int i = 0; i < entriesInPage; i++){ + int prop = 3 + i * 4; + + // PRN, ELEVATION, AZIMUTH, SNR + sat.prn = (uint32_t)parseInt(nmea.parameters[prop]); + sat.elevation = (uint32_t)parseInt(nmea.parameters[prop + 1]); + sat.azimuth = (uint32_t)parseInt(nmea.parameters[prop + 2]); + sat.snr = (uint32_t)parseInt(nmea.parameters[prop + 3]); + + //cout << "ADDING SATELLITE ::" << sat.toString() << endl; + this->fix.almanac.updateSatellite(sat); + } + + this->fix.almanac.processedPages++; + + // + if (this->fix.visibleSatellites == 0){ + this->fix.almanac.clear(); + } + + + //cout << "ALMANAC FINISHED page " << this->fix.almanac.processedPages << " of " << this->fix.almanac.totalPages << endl; + this->onUpdate(); + + } + catch (NumberConversionError& ex) + { + NMEAParseError pe("GPS Number Bad Format [$GPGSV] :: " + ex.message, nmea); + throw pe; + } + catch (NMEAParseError& ex) + { + NMEAParseError pe("GPS Data Bad Format [$GPGSV] :: " + ex.message, nmea); + throw pe; + } +} + +void GPSService::read_GPRMC(const NMEASentence& nmea){ + /* -- EXAMPLE --- + $GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A + $GPRMC,235957.025,V,,,,,,,070810,,,N*4B + $GPRMC,061425.000,A,3346.2243,N,08423.4706,W,0.45,18.77,060914,,,A*47 + + Where: + RMC Recommended Minimum sentence C + [0] 123519 Fix taken at 12:35:19 UTC + [1] A Status A=active or V=Void. + [2-3] 4807.038,N Latitude 48 deg 07.038' N + [4-5] 01131.000,E Longitude 11 deg 31.000' E + [6] 022.4 Speed over the ground in knots + [7] 084.4 Track angle in degrees True + [8] 230394 Date - 23rd of March 1994 + [9-10] 003.1,W Magnetic Variation + [10] *6A The checksum data, always begins with * + // NMEA 2.3 includes another field after + */ + + try + { + if (!nmea.checksumOK()){ + throw NMEAParseError("Checksum is invalid!"); + } + + if (nmea.parameters.size() < 11){ + throw NMEAParseError("GPS data is missing parameters."); + } + + // TIMESTAMP + this->fix.timestamp.setTime(parseDouble(nmea.parameters[0])); + + string sll; + string dir; + // LAT + sll = nmea.parameters[2]; + dir = nmea.parameters[3]; + if (sll.size() > 0){ + this->fix.latitude = convertLatLongToDeg(sll, dir); + } + + // LONG + sll = nmea.parameters[4]; + dir = nmea.parameters[5]; + if (sll.size() > 0){ + this->fix.longitude = convertLatLongToDeg(sll, dir); + } + + + // ACTIVE + bool lockupdate = false; + char status = 'V'; + if (nmea.parameters[1].size() > 0){ + status = nmea.parameters[1][0]; + } + this->fix.status = status; + if (status == 'V'){ + lockupdate = this->fix.setlock(false); + } + else if (status == 'A') { + lockupdate = this->fix.setlock(true); + } + else { + lockupdate = this->fix.setlock(false); //not A or V, so must be wrong... no lock + } + + + this->fix.speed = convertKnotsToKilometersPerHour(parseDouble(nmea.parameters[6])); // received as knots, convert to km/h + this->fix.travelAngle = parseDouble(nmea.parameters[7]); + this->fix.timestamp.setDate((int32_t)parseInt(nmea.parameters[8])); + + + //calling handlers + if (lockupdate){ + this->onLockStateChanged(this->fix.haslock); + } + this->onUpdate(); + } + catch (NumberConversionError& ex) + { + NMEAParseError pe("GPS Number Bad Format [$GPRMC] :: " + ex.message, nmea); + throw pe; + } + catch (NMEAParseError& ex) + { + NMEAParseError pe("GPS Data Bad Format [$GPRMC] :: " + ex.message, nmea); + throw pe; + } +} + +void GPSService::read_GPVTG(const NMEASentence& nmea){ + /* + $GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48 + + where: + VTG Track made good and ground speed + [0-1] 054.7,T True track made good (degrees) + [2-3] 034.4,M Magnetic track made good + [4-5] 005.5,N Ground speed, knots + [6-7] 010.2,K Ground speed, Kilometers per hour + [7] *48 Checksum + */ + + try + { + if (!nmea.checksumOK()){ + throw NMEAParseError("Checksum is invalid!"); + } + + if (nmea.parameters.size() < 8){ + throw NMEAParseError("GPS data is missing parameters."); + } + + // SPEED + // if empty, is converted to 0 + this->fix.speed = parseDouble(nmea.parameters[6]); //km/h + + + this->onUpdate(); + } + catch (NumberConversionError& ex) + { + NMEAParseError pe("GPS Number Bad Format [$GPVTG] :: " + ex.message, nmea); + throw pe; + } + catch (NMEAParseError& ex) + { + NMEAParseError pe("GPS Data Bad Format [$GPVTG] :: " + ex.message, nmea); + throw pe; + } +} + |
